Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
86f7d01
chore: Migrate even more tests
sentrivana Sep 11, 2026
6e713bb
more
sentrivana Sep 11, 2026
2330477
not yet
sentrivana Sep 11, 2026
8e93983
ref: Rewire top-level tracing API to span streaming
sentrivana Sep 11, 2026
51e4b4e
formatting
sentrivana Sep 11, 2026
9d22ba3
more instances
sentrivana Sep 11, 2026
7a6a7c6
.
sentrivana Sep 11, 2026
188e485
.
sentrivana Sep 11, 2026
306c251
.
sentrivana Sep 11, 2026
3482ec8
apidoc
sentrivana Sep 11, 2026
51a30f7
return trace
sentrivana Sep 11, 2026
fd3e215
remove span, transaction
sentrivana Sep 11, 2026
39ab5a0
remove compat funcs
sentrivana Sep 11, 2026
3d7349d
apidoc fix
sentrivana Sep 11, 2026
9981017
transactions events
sentrivana Sep 11, 2026
ed330a8
more transaction events
sentrivana Sep 11, 2026
cfd5920
.
sentrivana Sep 11, 2026
452e47b
previous_total_spans
sentrivana Sep 11, 2026
626e6e0
more helpers
sentrivana Sep 11, 2026
5dc10a9
Merge branch 'major/3.0' into ivana/major/rip-out-old-tracing
sentrivana Sep 14, 2026
f6a1dc8
bad merge
sentrivana Sep 14, 2026
018e983
Merge branch 'major/3.0' into ivana/major/rip-out-old-tracing
sentrivana Sep 14, 2026
e177eaf
chore: Remove more old transaction stuff
sentrivana Sep 14, 2026
3c7b7f9
Merge branch 'major/3.0' into ivana/major/rip-out-old-tracing
sentrivana Sep 14, 2026
e91932d
Merge branch 'ivana/major/rip-out-old-tracing' into ivana/major/more-…
sentrivana Sep 14, 2026
591f4a7
Merge branch 'major/3.0' into ivana/major/more-old-tracing
sentrivana Sep 14, 2026
64d58a0
arq
sentrivana Sep 14, 2026
1876004
django
sentrivana Sep 14, 2026
7359503
.
sentrivana Sep 14, 2026
3d37ea9
.
sentrivana Sep 14, 2026
015e539
base64???
sentrivana Sep 14, 2026
7766b0a
more leftovers
sentrivana Sep 14, 2026
a2c9854
Merge branch 'major/3.0' into ivana/major/more-old-tracing
sentrivana Sep 14, 2026
34d7a47
.
sentrivana Sep 14, 2026
492bde5
Merge branch 'major/3.0' into ivana/major/more-old-tracing
sentrivana Sep 14, 2026
d92ea9c
Merge branch 'major/3.0' into ivana/major/more-old-tracing
sentrivana Sep 14, 2026
724608f
Merge branch 'major/3.0' into ivana/major/more-old-tracing
sentrivana Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ uv run ruff format tests sentry_sdk
We use `mypy` for typing our codebase. Use the following command to typecheck the source:

```
uv run --group typing mypy sentry_sdk
uv run --group typing mypy sentry_sdk
```

##### Install Coding Style Pre-commit Hooks
Expand Down Expand Up @@ -167,7 +167,7 @@ TESTPATH=tests/integrations/logging/test_logging.py uv run tox -e py3.12-common
To select specific tests, you can forward arguments to `pytest` like so

```bash
uv run tox -e py3.12-celery-v5.5.3 -- -k test_transaction_events
uv run tox -e py3.12-celery-v5.5.3 -- -k test_simple_with_performance
```

In general, you use
Expand Down
1 change: 1 addition & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
- The `update_current_span` API was removed.
- `SanicIntegration` no longer accepts `unsampled_statuses`.
- The `trace_ignore_status_codes` option was removed.
- `add_attachment` no longer accepts an `add_to_transactions` argument.


## Deprecated
Expand Down
1 change: 0 additions & 1 deletion sentry_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,6 @@ class DataCollection(TypedDict):
"default",
"error",
"crash",
"transaction",
"security",
"attachment",
"session",
Expand Down
5 changes: 1 addition & 4 deletions sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,8 @@ def add_attachment(
filename: "Optional[str]" = None,
path: "Optional[str]" = None,
content_type: "Optional[str]" = None,
add_to_transactions: bool = False,
) -> None:
return get_isolation_scope().add_attachment(
bytes, filename, path, content_type, add_to_transactions
)
return get_isolation_scope().add_attachment(bytes, filename, path, content_type)


@scopemethod
Expand Down
6 changes: 1 addition & 5 deletions sentry_sdk/attachments.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ class Attachment:

This class stores attachments that can be sent along with an event. Attachments are files or other data, e.g.
config or log files, that are relevant to an event. Attachments are set on the ``Scope``, and are sent along with
all non-transaction events (or all events including transactions if ``add_to_transactions`` is ``True``) that are
captured within the ``Scope``.
all non-transaction events that are captured within the ``Scope``.

To add an attachment to a ``Scope``, use :py:meth:`sentry_sdk.Scope.add_attachment`. The parameters for
``add_attachment`` are the same as the parameters for this class's constructor.
Expand All @@ -25,7 +24,6 @@ class Attachment:
:param path: Path to a file to attach. Must be provided unless ``bytes`` is provided.
:param content_type: The content type of the attachment. If not provided, it will be guessed from the ``filename``
parameter, if available, or the ``path`` parameter if ``filename`` is ``None``.
:param add_to_transactions: Whether to add this attachment to transactions. Defaults to ``False``.
"""

def __init__(
Expand All @@ -34,7 +32,6 @@ def __init__(
filename: "Optional[str]" = None,
path: "Optional[str]" = None,
content_type: "Optional[str]" = None,
add_to_transactions: bool = False,
) -> None:
if bytes is None and path is None:
raise TypeError("path or raw bytes required for attachment")
Expand All @@ -48,7 +45,6 @@ def __init__(
self.filename = filename
self.path = path
self.content_type = content_type
self.add_to_transactions = add_to_transactions

def to_envelope_item(self) -> "Item":
"""Returns an envelope item for this attachment."""
Expand Down
209 changes: 3 additions & 206 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import json
import os
import platform
import random
import socket
import sys
import uuid
from collections.abc import Iterable, Mapping
from collections.abc import Mapping
from contextvars import ContextVar
from datetime import datetime, timezone
from importlib import import_module
from typing import TYPE_CHECKING, Dict, List, cast, overload
from typing import TYPE_CHECKING, Dict, cast, overload

from sentry_sdk._compat import check_uwsgi_thread_support
from sentry_sdk._log_batcher import LogBatcher
Expand All @@ -18,7 +15,6 @@
from sentry_sdk.consts import (
DEFAULT_MAX_VALUE_LENGTH,
DEFAULT_OPTIONS,
SPANSTATUS,
VERSION,
ClientConstructor,
)
Expand All @@ -34,7 +30,7 @@
from sentry_sdk.scrubber import EventScrubber
from sentry_sdk.serializer import serialize
from sentry_sdk.sessions import SessionFlusher
from sentry_sdk.traces import SpanStatus, StreamedSpan, trace
from sentry_sdk.traces import StreamedSpan, trace
from sentry_sdk.transport import (
AsyncHttpTransport,
HttpTransportCore,
Expand All @@ -44,7 +40,6 @@
AnnotatedValue,
capture_internal_exceptions,
current_stacktrace,
datetime_from_isoformat,
env_to_bool,
format_timestamp,
get_before_send_log,
Expand All @@ -70,7 +65,6 @@
Log,
Metric,
SDKInfo,
SerializedAttributeValue,
)
from sentry_sdk.integrations import Integration
from sentry_sdk.scope import Scope
Expand All @@ -92,203 +86,6 @@
}


def _serialized_v1_attribute_to_serialized_v2_attribute(
attribute_value: "Any",
) -> "Optional[SerializedAttributeValue]":
if isinstance(attribute_value, bool):
return {
"value": attribute_value,
"type": "boolean",
}

if isinstance(attribute_value, int):
return {
"value": attribute_value,
"type": "integer",
}

if isinstance(attribute_value, float):
return {
"value": attribute_value,
"type": "double",
}

if isinstance(attribute_value, str):
return {
"value": attribute_value,
"type": "string",
}

if isinstance(attribute_value, list):
if not attribute_value:
return {"value": [], "type": "array"}

ty = type(attribute_value[0])
if ty in (int, str, bool, float) and all(
type(v) is ty for v in attribute_value
):
return {
"value": attribute_value,
"type": "array",
}

# Types returned when the serializer for V1 span attributes recurses into some container types.
if isinstance(attribute_value, (dict, list)):
return {
"value": json.dumps(attribute_value),
"type": "string",
}

return None


def _serialized_v1_span_to_serialized_v2_span(
span: "dict[str, Any]", event: "Event"
) -> "dict[str, Any]":
# See SpanBatcher._to_transport_format() for analogous population of all entries except "attributes".
res: "dict[str, Any]" = {
"status": SpanStatus.OK.value,
"is_segment": False,
}

if "trace_id" in span:
res["trace_id"] = span["trace_id"]

if "span_id" in span:
res["span_id"] = span["span_id"]

if "description" in span:
description = span["description"]

if description is None and "op" in span:
description = span["op"]

res["name"] = description

if "start_timestamp" in span:
start_timestamp = None
try:
start_timestamp = datetime_from_isoformat(span["start_timestamp"])
except Exception:
pass

if start_timestamp is not None:
res["start_timestamp"] = start_timestamp.timestamp()

if "timestamp" in span:
end_timestamp = None
try:
end_timestamp = datetime_from_isoformat(span["timestamp"])
except Exception:
pass

if end_timestamp is not None:
res["end_timestamp"] = end_timestamp.timestamp()

if "parent_span_id" in span:
res["parent_span_id"] = span["parent_span_id"]

if "status" in span and span["status"] != SPANSTATUS.OK:
res["status"] = "error"

attributes: "Dict[str, Any]" = {}

if "op" in span:
attributes["sentry.op"] = span["op"]
if "origin" in span:
attributes["sentry.origin"] = span["origin"]

span_data = span.get("data")
if isinstance(span_data, dict):
attributes.update(span_data)

span_tags = span.get("tags")
if isinstance(span_tags, dict):
attributes.update(span_tags)

# See Scope._apply_user_attributes_to_telemetry() for user attributes.
user = event.get("user")
if isinstance(user, dict):
if "id" in user:
attributes["user.id"] = user["id"]
if "username" in user:
attributes["user.name"] = user["username"]
if "email" in user:
attributes["user.email"] = user["email"]

# See Scope.set_global_attributes() for release, environment, and SDK metadata.
if "release" in event:
attributes["sentry.release"] = event["release"]
if "environment" in event:
attributes["sentry.environment"] = event["environment"]
if "server_name" in event:
attributes["server.address"] = event["server_name"]
if "transaction" in event:
attributes["sentry.segment.name"] = event["transaction"]

trace_context = event.get("contexts", {}).get("trace", {})
if "span_id" in trace_context:
attributes["sentry.segment.id"] = trace_context["span_id"]

sdk_info = event.get("sdk")
if isinstance(sdk_info, dict):
if "name" in sdk_info:
attributes["sentry.sdk.name"] = sdk_info["name"]
if "version" in sdk_info:
attributes["sentry.sdk.version"] = sdk_info["version"]

attributes["process.runtime.name"] = platform.python_implementation()
attributes["process.runtime.version"] = (
f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
)

if not attributes:
return res

res["attributes"] = {}
for key, value in attributes.items():
converted_value = _serialized_v1_attribute_to_serialized_v2_attribute(value)
if converted_value is None:
continue

res["attributes"][key] = converted_value

# Remove redundant attribute, as status is stored in the status field.
if "status" in res["attributes"]:
del res["attributes"]["status"]

return res


def _split_gen_ai_spans(
event_opt: "Event",
) -> "Optional[tuple[List[Dict[str, object]], List[Dict[str, object]]]]":
if "spans" not in event_opt:
return None

spans: "Any" = event_opt["spans"]
if isinstance(spans, AnnotatedValue):
spans = spans.value

if not isinstance(spans, Iterable):
return None

non_gen_ai_spans = []
gen_ai_spans = []
for span in spans:
if not isinstance(span, dict):
non_gen_ai_spans.append(span)
continue

span_op = span.get("op")
if isinstance(span_op, str) and span_op.startswith("gen_ai."):
gen_ai_spans.append(span)
else:
non_gen_ai_spans.append(span)

return non_gen_ai_spans, gen_ai_spans


def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
if args and (isinstance(args[0], (bytes, str)) or args[0] is None):
dsn: "Optional[str]" = args[0]
Expand Down
Loading
Loading