Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
757ee12
feat: Span streaming & new span API
sentrivana Jan 15, 2026
705790a
More refactor, fixing some types
sentrivana Jan 15, 2026
b3f4f98
Cant use sentry_sdk.trace as that already exists
sentrivana Jan 15, 2026
758cdf3
Merge branch 'master' into feat/span-first-2
sentrivana Jan 15, 2026
97cf291
bubu
sentrivana Jan 15, 2026
0d2097b
dsc, sampling
sentrivana Jan 15, 2026
b01caab
.
sentrivana Jan 16, 2026
e946df6
.
sentrivana Jan 16, 2026
c47a0d0
fix? some types
sentrivana Jan 16, 2026
131f61c
safeguards, some type fixes
sentrivana Jan 16, 2026
40878e3
.
sentrivana Jan 16, 2026
3f985c4
move to traces.py
sentrivana Jan 20, 2026
d9874db
fix multiple envelopes being sent from the batcher
sentrivana Jan 20, 2026
a173352
send outside of lock
sentrivana Jan 20, 2026
fcb8ae5
.
sentrivana Jan 20, 2026
bc2fd79
old py
sentrivana Jan 20, 2026
73c6b73
more old py
sentrivana Jan 20, 2026
8519669
Merge branch 'master' into feat/span-first-2
sentrivana Jan 21, 2026
d40367d
trace propagation
sentrivana Jan 22, 2026
89d6084
fix tracing utils
sentrivana Jan 22, 2026
0e8ab89
move stuff around
sentrivana Jan 22, 2026
4b5c205
profiler, source, op
sentrivana Jan 22, 2026
546c2f0
prepare for profiler changes
sentrivana Jan 22, 2026
9b2b592
source, segment attrs/props
sentrivana Jan 22, 2026
0e198f2
add todo
sentrivana Jan 22, 2026
dca0870
profiler fixes, asgi first pass, sampling on start
sentrivana Jan 22, 2026
68cf5d2
.
sentrivana Jan 22, 2026
dedfaf0
starlette
sentrivana Jan 22, 2026
3ca46dc
asgi fixes, set_origin
sentrivana Jan 22, 2026
6673978
stdlib
sentrivana Jan 22, 2026
e72d3cd
httpx fix
sentrivana Jan 22, 2026
016c341
sqlalchemy
sentrivana Jan 22, 2026
802c7e9
fix
sentrivana Jan 22, 2026
7c35261
ctx mng things
sentrivana Jan 22, 2026
3f2747c
fix
sentrivana Jan 22, 2026
be9094b
mypy
sentrivana Jan 22, 2026
33deedd
more mypy
sentrivana Jan 22, 2026
6374a2a
migrate huggingface_hub
sentrivana Jan 22, 2026
481e7b6
more mypy, more integrations
sentrivana Jan 22, 2026
7ae020a
more mypy
sentrivana Jan 22, 2026
e90c182
missing import
sentrivana Jan 23, 2026
a44bbf4
missing import
sentrivana Jan 23, 2026
1d57bb6
asgi fixes
sentrivana Jan 23, 2026
c1271c1
asgi fixes
sentrivana Jan 23, 2026
ddbd34f
celery
sentrivana Jan 23, 2026
d1a0261
mypy in celery
sentrivana Jan 23, 2026
e4d2ab8
asgi mypy
sentrivana Jan 23, 2026
e954346
mypy
sentrivana Jan 23, 2026
25e202d
fix apidocs
sentrivana Jan 23, 2026
78708bc
Merge branch 'master' into feat/span-first-2
sentrivana Jan 23, 2026
60c135c
fixes
sentrivana Jan 23, 2026
d7ae85f
unused import
sentrivana Jan 23, 2026
ab49b1c
revert empty line
sentrivana Jan 23, 2026
c0bf515
simple tests
sentrivana Jan 23, 2026
2383afa
continue_trace tests
sentrivana Jan 23, 2026
a53a1c2
more tests
sentrivana Jan 23, 2026
3737b3c
more tests
sentrivana Jan 23, 2026
0d70d04
more asserts
sentrivana Jan 23, 2026
81c3b1f
Merge branch 'master' into feat/span-first-2
sentrivana Jan 23, 2026
b1e0d3f
small fixes
sentrivana Jan 27, 2026
7d0705c
more tests
sentrivana Jan 27, 2026
b91a1cf
another test
sentrivana Jan 27, 2026
371b1a5
mypy
sentrivana Jan 27, 2026
1767b1c
Merge branch 'master' into feat/span-first-2
sentrivana Jan 27, 2026
01f664f
release: 2.51.0a1
sentrivana Jan 28, 2026
880e2c1
Merge branch 'release/2.51.0a1' into feat/span-first-2
Jan 28, 2026
54830ec
Merge branch 'master' into feat/span-first-2
sentrivana Jan 29, 2026
590ed1c
add support for custom sampling context
sentrivana Jan 29, 2026
b7e508e
another custom sampling context test
sentrivana Jan 29, 2026
2a1dafb
scope fix, notes update
sentrivana Jan 29, 2026
f916f33
change sampling context
sentrivana Jan 29, 2026
192c75a
ignore_spans
sentrivana Jan 29, 2026
aef6512
more ignore spans
sentrivana Jan 29, 2026
e0cf425
Merge branch 'master' into feat/span-first-2
sentrivana Jan 29, 2026
7306887
Merge branch 'master' into feat/span-first-2
sentrivana Jan 30, 2026
2ea18ef
reimplement ignore_spans
sentrivana Jan 30, 2026
0518f56
fix is_ignored_span
sentrivana Jan 30, 2026
abedeaa
add case to ignore_spans tests
sentrivana Jan 30, 2026
ee2dbcd
Merge branch 'master' into feat/span-first-2
sentrivana Jan 30, 2026
41a7415
release: 2.52.0a1
sentrivana Feb 2, 2026
6c7cf70
Merge branch 'release/2.52.0a1' into feat/span-first-2
Feb 2, 2026
0f5b059
merge
sentrivana Feb 2, 2026
4c8f8e8
Merge branch 'master' into feat/span-first-2
sentrivana Feb 2, 2026
b9f148c
add shadow kwargs
sentrivana Feb 2, 2026
82cce87
.
sentrivana Feb 2, 2026
565d3f4
.
sentrivana Feb 2, 2026
35a808b
release: 2.52.0a2
sentrivana Feb 2, 2026
bcae7b9
Merge branch 'release/2.52.0a2' into feat/span-first-2
Feb 2, 2026
31cd6f9
Merge branch 'master' into feat/span-first-2
sentrivana Feb 2, 2026
5648696
add span.end()
sentrivana Feb 2, 2026
7947889
.
sentrivana Feb 2, 2026
726dc6c
Merge branch 'master' into feat/span-first-2
sentrivana Feb 2, 2026
1f8c395
Merge branch 'master' into feat/span-first-2
sentrivana Feb 2, 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
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,60 @@
# Changelog

## 2.52.0a2

### New Features ✨

#### Span Streaming

- feat(span-streaming): Add spans to telemetry pipeline, add span name and attributes (3) by @sentrivana in [#5399](https://github.com/getsentry/sentry-python/pull/5399)
- feat(span-streaming): Add span batcher (2) by @sentrivana in [#5398](https://github.com/getsentry/sentry-python/pull/5398)

### Internal Changes 🔧

- test(mcp): Simulate stdio transport with memory streams by @alexander-alderman-webb in [#5329](https://github.com/getsentry/sentry-python/pull/5329)

### Other

- [do not merge] feat: Span streaming & new span API by @sentrivana in [#5317](https://github.com/getsentry/sentry-python/pull/5317)

## 2.52.0a1

### New Features ✨

#### Openai

- feat(openai): Set system instruction attribute for Responses API by @alexander-alderman-webb in [#5376](https://github.com/getsentry/sentry-python/pull/5376)
- feat(openai): Set system instruction attribute for Completions API by @alexander-alderman-webb in [#5358](https://github.com/getsentry/sentry-python/pull/5358)

#### Other

- feat(ai): Add original input length meta attribute by @alexander-alderman-webb in [#5375](https://github.com/getsentry/sentry-python/pull/5375)
- feat(openai-agents): Set system instruction attribute on `gen_ai.chat` spans by @alexander-alderman-webb in [#5370](https://github.com/getsentry/sentry-python/pull/5370)
- feat(span-streaming): Add experimental `trace_lifecycle` switch (1) by @sentrivana in [#5397](https://github.com/getsentry/sentry-python/pull/5397)
- feat(transport): Report 413 responses for oversized envelopes by @alexander-alderman-webb in [#5380](https://github.com/getsentry/sentry-python/pull/5380)

### Bug Fixes 🐛

- fix(ai): Keep single content input message by @alexander-alderman-webb in [#5345](https://github.com/getsentry/sentry-python/pull/5345)
- fix(arq): handle settings_cls passed as keyword argument by @nc9 in [#5393](https://github.com/getsentry/sentry-python/pull/5393)
- fix(dramatiq): cleanup isolated scope and transaction when message is skipped by @frankie567 in [#5346](https://github.com/getsentry/sentry-python/pull/5346)
- fix(google-genai): deactivate google genai when langchain is used by @shellmayr in [#5389](https://github.com/getsentry/sentry-python/pull/5389)

### Internal Changes 🔧

- ci: migration to the new codecov action by @MathurAditya724 in [#5392](https://github.com/getsentry/sentry-python/pull/5392)
- ref: Replace `set_data_normalized()` with `Span.set_data()` for system instructions by @alexander-alderman-webb in [#5374](https://github.com/getsentry/sentry-python/pull/5374)

### Other

- [do not merge] feat: Span streaming & new span API by @sentrivana in [#5317](https://github.com/getsentry/sentry-python/pull/5317)
- Update CHANGELOG.md by @alexander-alderman-webb in [8517eb0a](https://github.com/getsentry/sentry-python/commit/8517eb0a0750796ae73d0e4c020b0a71c7724d0a)
- release: 2.51.0 by @alexander-alderman-webb in [93e89e4c](https://github.com/getsentry/sentry-python/commit/93e89e4c1b7e837c03dd62a81951e56634d4a9c0)

## 2.51.0a1

This is an alpha release for internal testing of span streaming.

## 2.51.0

### New Features ✨
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year)
author = "Sentry Team and Contributors"

release = "2.51.0"
release = "2.52.0a2"
version = ".".join(release.split(".")[:2]) # The short X.Y version.


Expand Down
28 changes: 19 additions & 9 deletions sentry_sdk/_span_batcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
from typing import TYPE_CHECKING

from sentry_sdk._batcher import Batcher
from sentry_sdk.consts import SPANSTATUS
from sentry_sdk.envelope import Envelope, Item, PayloadRef
from sentry_sdk.utils import format_timestamp, serialize_attribute, safe_repr
from sentry_sdk.utils import format_timestamp, serialize_attribute

if TYPE_CHECKING:
from typing import Any, Callable, Optional
from sentry_sdk.traces import StreamedSpan
from sentry_sdk._types import SerializedAttributeValue


class SpanBatcher(Batcher["StreamedSpan"]):
Expand Down Expand Up @@ -69,11 +67,25 @@ def add(self, span: "StreamedSpan") -> None:

@staticmethod
def _to_transport_format(item: "StreamedSpan") -> "Any":
# TODO[span-first]
res: "dict[str, Any]" = {
"name": item.name,
"trace_id": item.trace_id,
"span_id": item.span_id,
"name": item.get_name(),
"status": item.status,
"is_segment": item.is_segment(),
"start_timestamp": item.start_timestamp.timestamp(), # TODO[span-first]
}

if item.timestamp:
res["end_timestamp"] = item.timestamp.timestamp()

if item._last_valid_parent_id:
# This span needs to be reparented because its parent is ignored
res["parent_span_id"] = item._last_valid_parent_id

elif item.parent_span_id:
res["parent_span_id"] = item.parent_span_id

if item._attributes:
res["attributes"] = {
k: serialize_attribute(v) for (k, v) in item._attributes.items()
Expand All @@ -87,11 +99,9 @@ def _flush(self) -> None:
return None

envelopes = []
for trace_id, spans in self._span_buffer.items():
for spans in self._span_buffer.values():
if spans:
# TODO[span-first]
# dsc = spans[0].dynamic_sampling_context()
dsc = None
dsc = spans[0].dynamic_sampling_context()

envelope = Envelope(
headers={
Expand Down
10 changes: 9 additions & 1 deletion sentry_sdk/_types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, TypeVar, Union
from typing import TYPE_CHECKING, Pattern, TypeVar, Union


# Re-exported for compat, since code out there in the wild might use this variable.
Expand Down Expand Up @@ -363,3 +363,11 @@ class SDKInfo(TypedDict):
class TextPart(TypedDict):
type: Literal["text"]
content: str

IgnoreSpansName = Union[str, Pattern[str]]
IgnoreSpansContext = TypedDict(
"IgnoreSpansContext",
{"name": IgnoreSpansName, "attributes": Attributes},
total=False,
)
IgnoreSpansConfig = list[Union[IgnoreSpansName, IgnoreSpansContext]]
22 changes: 14 additions & 8 deletions sentry_sdk/ai/monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sentry_sdk.utils
from sentry_sdk import start_span
from sentry_sdk.tracing import Span
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.utils import ContextVar, reraise, capture_internal_exceptions

from typing import TYPE_CHECKING
Expand Down Expand Up @@ -97,7 +98,7 @@ async def async_wrapped(*args: "Any", **kwargs: "Any") -> "Any":


def record_token_usage(
span: "Span",
span: "Union[Span, StreamedSpan]",
input_tokens: "Optional[int]" = None,
input_tokens_cached: "Optional[int]" = None,
input_tokens_cache_write: "Optional[int]" = None,
Expand All @@ -106,30 +107,35 @@ def record_token_usage(
total_tokens: "Optional[int]" = None,
) -> None:
# TODO: move pipeline name elsewhere
if isinstance(span, StreamedSpan):
set_on_span = span.set_attribute
else:
set_on_span = span.set_data

ai_pipeline_name = get_ai_pipeline_name()
if ai_pipeline_name:
span.set_data(SPANDATA.GEN_AI_PIPELINE_NAME, ai_pipeline_name)
set_on_span(SPANDATA.GEN_AI_PIPELINE_NAME, ai_pipeline_name)

if input_tokens is not None:
span.set_data(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
set_on_span(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, input_tokens)

if input_tokens_cached is not None:
span.set_data(
set_on_span(
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED,
input_tokens_cached,
)

if input_tokens_cache_write is not None:
span.set_data(
set_on_span(
SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHE_WRITE,
input_tokens_cache_write,
)

if output_tokens is not None:
span.set_data(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
set_on_span(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)

if output_tokens_reasoning is not None:
span.set_data(
set_on_span(
SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS_REASONING,
output_tokens_reasoning,
)
Expand All @@ -138,4 +144,4 @@ def record_token_usage(
total_tokens = input_tokens + output_tokens

if total_tokens is not None:
span.set_data(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens)
set_on_span(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens)
23 changes: 19 additions & 4 deletions sentry_sdk/ai/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
from sentry_sdk._types import BLOB_DATA_SUBSTITUTE

if TYPE_CHECKING:
from typing import Any, Callable, Dict, List, Optional, Tuple
from typing import Any, Callable, Dict, List, Optional, Tuple, Union

from sentry_sdk.tracing import Span

import sentry_sdk
from sentry_sdk.utils import logger
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.tracing_utils import has_span_streaming_enabled
from sentry_sdk.consts import SPANDATA

MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB
Expand Down Expand Up @@ -490,13 +492,19 @@ def _normalize_data(data: "Any", unpack: bool = True) -> "Any":


def set_data_normalized(
span: "Span", key: str, value: "Any", unpack: bool = True
span: "Union[Span, StreamedSpan]", key: str, value: "Any", unpack: bool = True
) -> None:
normalized = _normalize_data(value, unpack=unpack)

if isinstance(span, StreamedSpan):
set_on_span = span.set_attribute
else:
set_on_span = span.set_data

if isinstance(normalized, (int, float, bool, str)):
span.set_data(key, normalized)
set_on_span(key, normalized)
else:
span.set_data(key, json.dumps(normalized))
set_on_span(key, json.dumps(normalized))


def normalize_message_role(role: str) -> str:
Expand Down Expand Up @@ -526,7 +534,14 @@ def normalize_message_roles(messages: "list[dict[str, Any]]") -> "list[dict[str,


def get_start_span_function() -> "Callable[..., Any]":
client = sentry_sdk.get_client()

current_span = sentry_sdk.get_current_span()
if isinstance(current_span, StreamedSpan) or has_span_streaming_enabled(
client.options
):
return sentry_sdk.traces.start_span

transaction_exists = (
current_span is not None and current_span.containing_transaction is not None
)
Expand Down
15 changes: 14 additions & 1 deletion sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sentry_sdk.consts import INSTRUMENTER
from sentry_sdk.scope import Scope, _ScopeManager, new_scope, isolation_scope
from sentry_sdk.tracing import NoOpSpan, Transaction, trace
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.crons import monitor

from typing import TYPE_CHECKING
Expand Down Expand Up @@ -385,7 +386,9 @@ def set_measurement(name: str, value: float, unit: "MeasurementUnit" = "") -> No
transaction.set_measurement(name, value, unit)


def get_current_span(scope: "Optional[Scope]" = None) -> "Optional[Span]":
def get_current_span(
scope: "Optional[Scope]" = None,
) -> "Optional[Union[Span, StreamedSpan]]":
"""
Returns the currently active span if there is one running, otherwise `None`
"""
Expand Down Expand Up @@ -501,6 +504,16 @@ def update_current_span(
if current_span is None:
return

if isinstance(current_span, StreamedSpan):
warnings.warn(
"The `update_current_span` API isn't available in streaming mode. "
"Retrieve the current span with get_current_span() and use its API "
"directly.",
DeprecationWarning,
stacklevel=2,
)
return

if op is not None:
current_span.op = op

Expand Down
1 change: 1 addition & 0 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ def _capture_telemetry(
before_send = get_before_send_log(self.options)
elif ty == "metric":
before_send = get_before_send_metric(self.options) # type: ignore
# no before_send for spans

if before_send is not None:
telemetry = before_send(telemetry, {}) # type: ignore
Expand Down
5 changes: 4 additions & 1 deletion sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class CompressionAlgo(Enum):
Dict,
List,
Optional,
Pattern,
Sequence,
Tuple,
Type,
Expand All @@ -52,6 +53,7 @@ class CompressionAlgo(Enum):
Event,
EventProcessor,
Hint,
IgnoreSpansConfig,
Log,
MeasurementUnit,
Metric,
Expand Down Expand Up @@ -83,6 +85,7 @@ class CompressionAlgo(Enum):
"enable_metrics": Optional[bool],
"before_send_metric": Optional[Callable[[Metric, Hint], Optional[Metric]]],
"trace_lifecycle": Optional[Literal["static", "stream"]],
"ignore_spans": Optional[IgnoreSpansConfig],
},
total=False,
)
Expand Down Expand Up @@ -1492,4 +1495,4 @@ def _get_default_options() -> "dict[str, Any]":
del _get_default_options


VERSION = "2.51.0"
VERSION = "2.52.0a2"
2 changes: 2 additions & 0 deletions sentry_sdk/envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ def data_category(self) -> "EventDataCategory":
return "session"
elif ty == "attachment":
return "attachment"
elif ty == "span":
return "span"
elif ty == "transaction":
return "transaction"
elif ty == "event":
Expand Down
Loading
Loading