Skip to content
Draft
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2be94ca
feat: Send GenAI spans as V2 envelope items
alexander-alderman-webb Apr 15, 2026
01f479a
.
alexander-alderman-webb Apr 15, 2026
80e6a10
.
alexander-alderman-webb Apr 15, 2026
0622cf4
.
alexander-alderman-webb Apr 15, 2026
7c75da1
.
alexander-alderman-webb Apr 15, 2026
54a9b07
update
alexander-alderman-webb Apr 15, 2026
d1aa07c
.
alexander-alderman-webb Apr 15, 2026
117a6c9
.
alexander-alderman-webb Apr 15, 2026
83c36b5
.
alexander-alderman-webb Apr 15, 2026
f71e0ce
openai tests
alexander-alderman-webb Apr 16, 2026
1fab632
anthropic tests
alexander-alderman-webb Apr 16, 2026
f44316d
google-genai tests
alexander-alderman-webb Apr 16, 2026
ff9c5ec
test litellm
alexander-alderman-webb Apr 17, 2026
b92ae36
test huggingface_hub
alexander-alderman-webb Apr 17, 2026
907ca1d
test langchain
alexander-alderman-webb Apr 17, 2026
b254297
test langgraph
alexander-alderman-webb Apr 17, 2026
6f7a054
accept any as sdk version
alexander-alderman-webb Apr 17, 2026
4f871a4
pydantic-ai tests
alexander-alderman-webb Apr 17, 2026
7befc7d
.
alexander-alderman-webb Apr 17, 2026
fb348bb
openai-agents tests
alexander-alderman-webb Apr 17, 2026
41e409d
fix openai-agents tests
alexander-alderman-webb Apr 17, 2026
8bf77f0
fix common tests
alexander-alderman-webb Apr 17, 2026
7c3da4f
client handle None
alexander-alderman-webb Apr 17, 2026
06c2a40
fix item_count
alexander-alderman-webb Apr 17, 2026
204b980
fix common tests
alexander-alderman-webb Apr 17, 2026
00733f9
fix common tests
alexander-alderman-webb Apr 17, 2026
a54cab4
common tests
alexander-alderman-webb Apr 17, 2026
4b0c47b
tests
alexander-alderman-webb Apr 17, 2026
6c5c812
add experimental v2 option
alexander-alderman-webb Apr 17, 2026
51a07ff
push experiment
alexander-alderman-webb Apr 17, 2026
bab7567
fix tests
alexander-alderman-webb Apr 17, 2026
3e55795
client changes
alexander-alderman-webb Apr 17, 2026
6d1d7ed
simplify client logic
alexander-alderman-webb Apr 17, 2026
6bf4006
Revert "add experimental v2 option"
alexander-alderman-webb Apr 17, 2026
700e8a1
retry adding experimental option to tests
alexander-alderman-webb Apr 17, 2026
9b20bd2
add experimental option to langgraph tests
alexander-alderman-webb Apr 17, 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
105 changes: 103 additions & 2 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
get_before_send_metric,
has_logs_enabled,
has_metrics_enabled,
serialize_attribute,
)
from sentry_sdk.serializer import serialize
from sentry_sdk.tracing import trace
Expand Down Expand Up @@ -56,6 +57,74 @@
)
from sentry_sdk.scrubber import EventScrubber
from sentry_sdk.monitor import Monitor
from sentry_sdk.envelope import Item, PayloadRef


_ISO_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"


def _iso_to_epoch(iso_str: str) -> float:
return (
datetime.strptime(iso_str, _ISO_TIMESTAMP_FORMAT)
.replace(tzinfo=timezone.utc)
.timestamp()
)


def _v1_span_to_v2(span: "Dict[str, Any]", event: "Dict[str, Any]") -> "Dict[str, Any]":
rv: "Dict[str, Any]" = {
"trace_id": span["trace_id"],
"span_id": span["span_id"],
"name": span.get("description") or "",
"is_segment": False,
"start_timestamp": _iso_to_epoch(span["start_timestamp"]),

Check warning on line 80 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: code-review

Unhandled exception in _v1_span_to_v2 can crash event capture

The `_v1_span_to_v2` function directly accesses `span["start_timestamp"]` without error handling. If a span has a missing or malformed timestamp (e.g., modified by a `before_send_transaction` callback), `datetime.strptime` will raise a `ValueError` or `KeyError`. This exception would propagate up and cause the entire transaction capture to fail, potentially losing all spans in the transaction.
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated
"status": "ok",
}

if span.get("timestamp"):
rv["end_timestamp"] = _iso_to_epoch(span["timestamp"])

if span.get("parent_span_id"):
rv["parent_span_id"] = span["parent_span_id"]

status = span.get("status")
if status and status != "ok":
rv["status"] = "error"

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

if span.get("op"):
attributes["sentry.op"] = span["op"]
if span.get("origin"):
attributes["sentry.origin"] = span["origin"]

for key, value in (span.get("data") or {}).items():
attributes[key] = value
for key, value in (span.get("tags") or {}).items():
attributes[key] = value

trace_context = event.get("contexts", {}).get("trace", {})
sdk_info = event.get("sdk", {})

if event.get("release"):
attributes["sentry.release"] = event["release"]
if event.get("environment"):
attributes["sentry.environment"] = event["environment"]
if event.get("transaction"):
attributes["sentry.segment.name"] = event["transaction"]

if trace_context.get("span_id"):
attributes["sentry.segment.id"] = trace_context["span_id"]
if sdk_info.get("name"):
attributes["sentry.sdk.name"] = sdk_info["name"]
if sdk_info.get("version"):
attributes["sentry.sdk.version"] = sdk_info["version"]

if attributes:
rv["attributes"] = {k: serialize_attribute(v) for k, v in attributes.items()}

return rv

Check warning on line 126 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: find-bugs

GenAI V2 spans use unprocessed event data, missing before_send_transaction modifications

The `_v1_span_to_v2` function on line 1009 is called with `event` instead of `event_opt`. The `event_opt` variable contains the fully processed event after serialization and `before_send_transaction` callback, while `event` is the original input. If a user's `before_send_transaction` callback modifies fields like `release`, `environment`, `transaction`, or `sdk` info, those changes will be reflected in the transaction envelope but NOT in the GenAI V2 span envelope items, causing data inconsistency between the transaction and its associated spans.
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated


if TYPE_CHECKING:
from typing import Any
Expand All @@ -72,7 +141,7 @@
from sentry_sdk.session import Session
from sentry_sdk.spotlight import SpotlightClient
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.transport import Transport, Item
from sentry_sdk.transport import Transport, Item, PayloadRef
from sentry_sdk._log_batcher import LogBatcher
from sentry_sdk._metrics_batcher import MetricsBatcher
from sentry_sdk.utils import Dsn
Expand Down Expand Up @@ -912,7 +981,39 @@
if is_transaction:
if isinstance(profile, Profile):
envelope.add_profile(profile.to_json(event_opt, self.options))
envelope.add_transaction(event_opt)

nonstreamed_spans = []
streamed_spans = []
for span in event_opt.get("spans") or []:
span_op = span.get("op")
if span_op is not None and span_op.startswith("gen_ai."):
streamed_spans.append(span)
else:
nonstreamed_spans.append(span)

if nonstreamed_spans:
event_opt["spans"] = nonstreamed_spans
envelope.add_transaction(event_opt)

Check failure on line 996 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: find-bugs

Transaction silently dropped when all spans are GenAI spans

When a transaction contains only spans with operations starting with "gen_ai.", the `nonstreamed_spans` list will be empty. The condition on line 994 (`if nonstreamed_spans:`) means `envelope.add_transaction(event_opt)` is never called. This silently drops the transaction data (transaction name, timestamps, trace context, etc.) while only sending the converted V2 spans. The backend may fail to correlate these orphaned spans without their parent transaction.

Check failure on line 996 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: code-review

Transaction is not captured when all child spans are gen_ai spans

The transaction (root span) is only added to the envelope when `nonstreamed_spans` is truthy (line 994-996). If all child spans have operations starting with "gen_ai.", `nonstreamed_spans` will be an empty list and `add_transaction()` is never called. This causes the entire transaction to be lost - only the gen_ai child spans are sent to Sentry without their parent transaction context.
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Outdated

if streamed_spans:
envelope.add_item(
Item(
type=SpanBatcher.TYPE,
content_type=SpanBatcher.CONTENT_TYPE,
headers={
"item_count": len(streamed_spans),
},
payload=PayloadRef(
json={
"items": [
_v1_span_to_v2(span, event)

Check warning on line 1009 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: find-bugs

[2LS-JUL] GenAI V2 spans use unprocessed event data, missing before_send_transaction modifications (additional location)

The `_v1_span_to_v2` function on line 1009 is called with `event` instead of `event_opt`. The `event_opt` variable contains the fully processed event after serialization and `before_send_transaction` callback, while `event` is the original input. If a user's `before_send_transaction` callback modifies fields like `release`, `environment`, `transaction`, or `sdk` info, those changes will be reflected in the transaction envelope but NOT in the GenAI V2 span envelope items, causing data inconsistency between the transaction and its associated spans.
for span in streamed_spans
]
},
),
)
)

elif is_checkin:
envelope.add_checkin(event_opt)
else:
Expand Down
Loading