Next Python SDK major - #5005
sentrivana wants to merge 256 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #5005 +/- ##
===========================================
+ Coverage 70.55% 83.76% +13.21%
===========================================
Files 180 180
Lines 18077 18080 +3
Branches 3008 3009 +1
===========================================
+ Hits 12754 15145 +2391
+ Misses 4432 1943 -2489
- Partials 891 992 +101
|
Codecov Results 📊✅ 66913 passed | ❌ 1 failed | ⏭️ 3019 skipped | Total: 69933 | Pass Rate: 95.68% | Execution Time: 189m 32s 📊 Comparison with Base Branch
➕ New Tests (1)View new tests
❌ Failed Tests
|
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛Anthropic
Documentation 📚
Internal Changes 🔧
Other
🤖 This preview updates automatically when you update the PR. |
Remove everything hub related, including all sorts of compatibility shims around hubs/scopes. Also remove deprecated session methods. `configure_scope` and `push_scope` removal coming in a future PR. #### Issues Closes #5001
The integration requires additional configuration which should be intentional on the user's part. #### Issues Closes #4993
- Remove everything in `integrations/opentelemetry` (`SentrySpanProcessor`, `SentryPropagator`, etc.) - Remove associated test files and CI config - Move old propagator functions and consts that we were using in `OTLPIntegration` to the OTLP propagator directly - Remove `instrumenter` Note: `NoOpSpan` was not removed because it makes mypy blow up. Not worth the effort as we'll anyway get rid of it when dropping transaction based tracing. #### Issues Closes #6932
### Description The API is deprecated and slated for removal in 3.0. #### Issues Closes #5019 #### Reminders - Please add tests to validate your changes, and lint your code using `uv run ruff`. - Add GH Issue ID _&_ Linear ID (if applicable) - PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `meta:`) - For external contributors: [CONTRIBUTING.md](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr)
### Description Remove the deprecated API. #### Issues Closes #5018
### Description Most of the entries in our extras list serve as a way to communicate/enforce the lower boundary of the respective framework that we support. This creates a parallel system to the version checks we already have in each integration. Some extras, however, define extra dependencies or specific extras that are required for an integration to work correctly (e.g. the Flask integration needs `blinker` to work properly). In that case, keep the extra. #### Issues Closes #6259
### Description See the original potel PR linked in the ticket for more context. #### Issues Closes https://linear.app/getsentry/issue/PY-1938/remove-spotlight-django-integration Closes #5014
- Remove the `Span` and `Transaction` classes and associated types - Remove transaction/old span support from transport, client, scope - Rewire top-level API to `sentry_sdk.traces.*` - Remove old span counting/reporting (`dropped_spans` etc.) - Remove `has_span_streaming_enabled` - ...and more It's a big diff but no logic changes, just removing anything old span related. Closes https://linear.app/getsentry/issue/PY-2654/rewire-top-level-tracing-api-to-span-streaming Closes https://linear.app/getsentry/issue/PY-2655/remove-legacy-tracing Closes https://linear.app/getsentry/issue/PY-2653/remove-has-span-streaming-enabled-branches-from-core
This original message count is never set since truncation for GenAI spans was removed.
Remove last mention of `set_data` (it's commented out, but still). Closes https://linear.app/getsentry/issue/PY-1946/deprecatedrop-set-data
… present (#7493) Use a boolean scope member to suppress chat generation spans from client libraries that are inside a chat generation span from an agent framework. Add the `traces._AgentFrameworkChatGenerationContext` wrapper to ensure the scope member is reset when the agent framework span exits. Remove logic that disables client library integrations when an agent library is active. Delete the `integration_deactivation` tox environment that ran the corresponding tests. Use the context manager in all agent frameworks that create chat generation spans (`langchain`, `pydantic-ai`, and `openai-agents`). Add early returns for chat generation spans in the `openai`, `anthropic`, and `google-genai` client libraries to prevent duplicate generation spans. Remove embedding patches from the `langchain` integration. These must now be provided by client libraries like `openai`, `anthropic`, etc. Closes #5515
- remove `scope.transaction` (`set_transaction_name` remains for now; changed all direct uses of `scope.transaction =` to use `set_transaction_name` instead) - remove more interop utils (`_serialized_v1_attribute_to_serialized_v2_attribute` etc.) - remove event processor-based transaction name setting (superseded by `set_transaction_name` directly in patches) - ...
…spans (#7512) Remove the response model from Invoke Agent spans because it is ambiguous for an agent that can call different models in the course of its execution.
…t spans (#7513) Remove the response model from Invoke Agent spans because it is ambiguous for an agent that can call different models in the course of its execution.
Remove token attributes from Invoke Agent spans because it is ambiguous whether they include usage from tool calls.
Remove token attributes from Invoke Agent spans because it is ambiguous whether they include usage from tool calls.
Also: remove remaining things from tracing.py and delete the file. Closes https://linear.app/getsentry/issue/PY-2771/switch-transactionsource-to-segmentnamesource-everywhere
| @@ -756,22 +735,21 @@ async def async_wrapped(*args: "Any", **kwargs: "Any") -> "Any": | |||
| @wraps(tool) | |||
| def sync_wrapped(*args: "Any", **kwargs: "Any") -> "Any": | |||
| with _create_tool_span(tool_name, tool_doc) as span: | |||
| set_on_span = ( | |||
| span.set_attribute | |||
| if isinstance(span, StreamedSpan) | |||
| else span.set_data | |||
| ) | |||
| # Capture tool input | |||
| tool_input = _capture_tool_input(args, kwargs, tool) | |||
| with capture_internal_exceptions(): | |||
| set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_input)) | |||
| span.set_attribute( | |||
| SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_input) | |||
| ) | |||
|
|
|||
| try: | |||
| result = tool(*args, **kwargs) | |||
|
|
|||
| # Capture tool output | |||
| with capture_internal_exceptions(): | |||
| set_on_span(SPANDATA.GEN_AI_TOOL_OUTPUT, safe_serialize(result)) | |||
| span.set_attribute( | |||
| SPANDATA.GEN_AI_TOOL_OUTPUT, safe_serialize(result) | |||
There was a problem hiding this comment.
Tool input/output always sent without PII/data_collection guards
wrapped_tool always serializes full tool args and return values onto spans, bypassing the gen_ai inputs/outputs and include_prompts checks used elsewhere in this integration. Gate GEN_AI_TOOL_INPUT/OUTPUT the same way prompts and response text are gated.
Evidence
- In
async_wrappedandsync_wrapped,_capture_tool_input(args, kwargs, tool)and the toolresultare passed tospan.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT/OUTPUT, safe_serialize(...))with no privacy check. - The same file gates prompts, response text, and related gen_ai content behind
has_data_collection_enabled(...gen_ai.inputs/outputs)orshould_send_default_pii() and integration.include_prompts(e.g.set_span_data_for_request/set_span_data_for_response). - Sibling AI integrations (LangChain, Pydantic AI, OpenAI Agents) only set tool input/output when those same guards allow it.
- Tool args/results are application-controlled and commonly contain user content, secrets, or PII, so they are sent to Sentry even when
send_default_pii=Falseordata_collection.gen_ai.*=False.
Identified by Warden · security-review, find-bugs, code-review · 59T-VTZ
|
|
||
| ## Changed | ||
|
|
||
| - The Strawberry integration won't auto-enable anymore if we detect `strawberry-graphql` is installed. Set it up manually, setting the `async_execution` integration option to either `True` or `False` depending on if your app is async or sync. | ||
|
|
||
| ```python | ||
| from sentry_sdk.integrations.strawberry import StrawberryIntegration | ||
|
|
||
| sentry_sdk.init( | ||
| integrations=[ | ||
| StrawberryIntegration(async_execution=True), # or False | ||
| ], | ||
| ... | ||
| ) | ||
| ``` | ||
|
|
||
| - The FastAPI and Starlette integrations no longer eagerly consume the request body. As a result, the body is only reported in events if your handler parsed it with `Request.json()` or `Request.form()` before the event is captured. | ||
| - The UnraisableHookIntegration is now enabled by default. | ||
| - We now don't suppress chained exceptions in the ASGI and asyncio integrations by default. The related `suppress_asgi_chained_exceptions` experimental option was removed. | ||
| - In the AWS Lambda and GCP integrations, the message of the warning the SDK optionally emits if a function is about to time out has changed. | ||
| - We changed the way we emit warnings. Deprecations will from now on be always emitted using `warnings.warn()`, while all other warnings will be emitted using `logger.warning()`. | ||
| - `sentry_sdk.init()` can no longer be used as a context manager. | ||
| - The `@trace` decorator doesn't accept a `template` parameter anymore. | ||
| - The option `attach_stacktrace` is now `True` by default, meaning the SDK will attach stack traces to messages. |
There was a problem hiding this comment.
Migration guide omits transaction-to-span-streaming API break
Document removal of start_transaction/Transaction and the start_span signature change (op/description → name/attributes), with concrete before/after examples—this is the headline 3.0 break and currently has no migration path here.
Evidence
- PR states transaction-based tracing is removed and span streaming is the default tracing model.
sentry_sdk/api.pyexportsstart_span,continue_trace, andnew_traceonly—nostart_transaction.sentry_sdk/traces.pystart_span(name=..., attributes=...)replaces the oldop/descriptionAPI;tracing.py/Transactionare gone.MIGRATION_GUIDE.mdChanged/Removed sections (lines 1–119+) never mentionstart_transaction,Transaction, or how to rewrite existing tracing calls.
Also found at 1 additional location
sentry_sdk/__init__.py:48-49
Identified by Warden · code-review · YNX-E65
| if sentry_sdk.get_current_scope()._agent_framework_chat_generation_entered: | ||
| return f(self, *args, **kwargs) |
There was a problem hiding this comment.
Async stream early-return drops await and returns a coroutine
When _agent_framework_chat_generation_entered is set, new_async_generate_content_stream returns f(...) instead of await f(...), so callers get a coroutine instead of the stream.
Evidence
- The surrounding path at line 140 correctly does
return await f(self, *args, **kwargs)when the integration is missing. - The new early-return at lines 142-143 does
return f(self, *args, **kwargs)with noawait. - Sibling async integrations (
openai.py,anthropic.py) await on the same_agent_framework_chat_generation_enteredguard.
Also found at 1 additional location
sentry_sdk/integrations/google_genai/__init__.py:245-246
Identified by Warden · code-review, find-bugs · QMM-3HM
|
|
||
| def _exit_span( | ||
| self: "SentryLangchainCallback", | ||
| span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", | ||
| span: "Span", | ||
| run_id: "UUID", | ||
| ) -> None: | ||
| span.__exit__(None, None, None) |
There was a problem hiding this comment.
Chat generation exit skips context cleanup and leaves suppression flag set
_exit_span only ends the inner Span, so successful on_chat_model_end paths never call _AgentFrameworkChatGenerationContext.__exit__() and leave _agent_framework_chat_generation_entered stuck true, suppressing later OpenAI/Anthropic/Google GenAI spans on that scope. Exit the stored context (as _handle_error already does) instead of unwrapping to the raw span.
Evidence
_create_generation_span()stores an_AgentFrameworkChatGenerationContextinspan_map; that class setsspan._scope._agent_framework_chat_generation_entered = Truein__init__and only clears it in__exit__._exit_span()was changed to accept only aSpanand callspan.__exit__(), which ends the span but does not clear the flag.on_chat_model_endunwrapscontext.spanand calls_exit_span(span, run_id), so the success path never invokescontext.__exit__()._handle_error()still callscontext.__exit__()correctly, so only successful chat completions leak the flag.- OpenAI/Anthropic/Google GenAI integrations skip instrumentation when
get_current_scope()._agent_framework_chat_generation_enteredis true.
Also found at 2 additional locations
sentry_sdk/integrations/langchain.py:537-561sentry_sdk/integrations/langchain.py:18-18
Identified by Warden · code-review, find-bugs · 7R5-TEG
| send_client_reports: bool = True, | ||
| _experiments: "Experiments" = {}, # noqa: B006 | ||
| proxy_headers: "Optional[Dict[str, str]]" = None, | ||
| instrumenter: "Optional[str]" = INSTRUMENTER.SENTRY, | ||
| before_send_transaction: "Optional[TransactionProcessor]" = None, | ||
| project_root: "Optional[str]" = None, | ||
| enable_tracing: "Optional[bool]" = None, | ||
| include_local_variables: "Optional[bool]" = True, | ||
| include_source_context: "Optional[bool]" = True, | ||
| trace_propagation_targets: "Optional[Sequence[str]]" = [ # noqa: B006 |
There was a problem hiding this comment.
Removed before_send_transaction option is undocumented and has no equivalent migration guidance
Existing applications that pass before_send_transaction to sentry_sdk.init() now fail during initialization with TypeError: Unknown option 'before_send_transaction'. The 3.0 migration guide does not document this removal or explain that transaction-level filtering is no longer available; before_send_span is not a direct equivalent because streamed spans cannot be dropped.
Evidence
ClientConstructor.__init__no longer declaresbefore_send_transaction, so it is absent fromDEFAULT_OPTIONSgenerated by_get_default_options()._get_options()rejects the legacy keyword withTypeError("Unknown option %r" % (key,))before the client can initialize.- The migration guide lists other removed options but does not mention
before_send_transactionor the loss of transaction-level filtering. Client._capture_telemetry()routes the replacement callback throughbefore_send_span, whose implementation explicitly keeps spans even when the callback returnsNone.
Identified by Warden · code-review · YR8-HD6
| if new_messages is None: | ||
| return | ||
|
|
||
| _set_usage_data(span, new_messages) | ||
| _set_response_model_name(span, new_messages) | ||
|
|
||
| if _should_record_outputs(integration): | ||
| llm_response_text = _extract_llm_response_text(new_messages) | ||
| if llm_response_text: |
There was a problem hiding this comment.
Langgraph agent spans lose usage and response model attributes
Removing _set_usage_data and _set_response_model_name drops gen_ai.usage.* and gen_ai.response.model from invoke_agent spans; if this was only StreamedSpan cleanup, keep setting them via set_attribute/set_data_normalized.
Evidence
- The hunk deletes
_set_usage_dataand_set_response_model_name, and removes their calls from_set_response_attributes. - Those helpers previously aggregated
response_metadata.token_usageandmodel_nameonto the langgraph invoke_agent span. _set_response_attributesstill only records response text/tool calls, so usage and model attributes are no longer set on this path.- Sibling AI integrations (e.g. langchain callbacks) still record usage/model on their own spans, so this is a langgraph-agent-span behavior change, not a global AI attribute removal.
Identified by Warden · code-review, find-bugs · GAY-67G
| include_pii=_should_record(integration, "outputs"), | ||
| ) | ||
| yield x | ||
| _end_span(span) | ||
| span.end() |
There was a problem hiding this comment.
Streaming iterator swallows user exceptions via capture_internal_exceptions
Wrap the stream loop so capture_internal_exceptions only guards SDK collection, not yield; otherwise stream errors are swallowed and the caller never sees them.
Evidence
new_iterator()wrapsfor x in old_iterator: ... yield xinwith capture_internal_exceptions().capture_internal_exceptions().__exit__returnsTrue, so any exception from the Cohere stream or from the generator is suppressed.span.end()still runs after the suppressed exception, so the caller gets a silent truncated stream and an OK span.- Sibling integrations (google_genai, langchain) re-raise stream exceptions and only use capture_internal_exceptions around telemetry.
Also found at 1 additional location
sentry_sdk/integrations/cohere.py:228-234
Identified by Warden · find-bugs, code-review · HCZ-A5T
| # This logger logs every status of every task that ran on the worker. | ||
| # Meaning that every task's breadcrumbs are full of stuff like "Task | ||
| # <foo> raised unexpected <bar>". | ||
| ignore_logger("celery.worker.job") | ||
| ignore_logger("celery.app.trace") | ||
| ignore_logger_for_events("celery.worker.job") | ||
| ignore_logger_for_events("celery.app.trace") | ||
|
|
||
| # This is stdout/err redirected to a logger, can't deal with this | ||
| # (need event_level=logging.WARN to reproduce) | ||
| ignore_logger("celery.redirected") | ||
| ignore_logger_for_events("celery.redirected") |
There was a problem hiding this comment.
Celery control-flow exceptions not registered, so task segment still ends as error
setup_once() never calls _register_control_flow_exception() for Celery Retry/Ignore/Reject, so the outer task segment still becomes error on span exit even when the exception is treated as non-error control flow.
Evidence
- Celery
setup_once()only patches tracers/workers and ignores loggers; it does not registerCELERY_CONTROL_FLOW_EXCEPTIONS. Span.__exit__setsSpanStatus.ERRORwhenevershould_be_treated_as_error(ty, value)is true.should_be_treated_as_error()only skips types previously passed to_register_control_flow_exception().- Huey and ARQ both register their control-flow exceptions in
setup_once(); Celery does not, so uncaughtRetry/Ignore/Rejectstill fail the outerqueue.task.celerysegment.
Also found at 1 additional location
sentry_sdk/integrations/celery/__init__.py:95-100
Identified by Warden · find-bugs, code-review · GQL-YD4
Change `cache_spans` to default to `True` and remove the Spotlight/DEBUG override, so the option is always respected as given. Fixes PY-154 Fixes #3300
Bottle seems to be the only web framework integration that sets the transaction name late, so errors that happen before that might not get associated to the correct segment name. We can't move the logic because we don't have enough information before that point. Restore the transaction setting logic in the event processor. Closes https://linear.app/getsentry/issue/PY-2784/double-check-that-set-transaction-name-is-set-early-enough
| span = sentry_sdk.traces.start_span( | ||
| name=f"invoke_agent {run_name}" if run_name else "invoke_agent", | ||
| attributes={ | ||
| "sentry.op": OP.GEN_AI_INVOKE_AGENT, | ||
| "sentry.origin": LangchainIntegration.origin, | ||
| SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", | ||
| SPANDATA.GEN_AI_RESPONSE_STREAMING: True, | ||
| }, | ||
| ) | ||
|
|
||
| if run_name: | ||
| span.set_data(SPANDATA.GEN_AI_FUNCTION_ID, run_name) | ||
| if run_name: | ||
| span.set_attribute(SPANDATA.GEN_AI_FUNCTION_ID, run_name) | ||
|
|
||
| _set_tools_on_span(span, tools) | ||
|
|
||
| input = args[0].get("input") if len(args) >= 1 else None | ||
| if input is not None and record_inputs: | ||
| normalized_messages = normalize_message_roles([input]) | ||
|
|
||
| scope = sentry_sdk.get_current_scope() | ||
| messages_data = ( | ||
| truncate_and_annotate_messages(normalized_messages, span, scope) | ||
| if should_truncate_gen_ai_input(client.options) | ||
| else normalized_messages | ||
| set_data_normalized( | ||
| span, | ||
| SPANDATA.GEN_AI_REQUEST_MESSAGES, | ||
| normalized_messages, | ||
| unpack=False, | ||
| ) | ||
| if messages_data is not None: | ||
| set_data_normalized( | ||
| span, | ||
| SPANDATA.GEN_AI_REQUEST_MESSAGES, | ||
| messages_data, | ||
| unpack=False, | ||
| ) | ||
|
|
||
| # Run the agent | ||
| result = f(self, *args, **kwargs) |
There was a problem hiding this comment.
AgentExecutor.stream leaves invoke_agent span active if setup raises
Wrap result = f(self, *args, **kwargs) in try/except and call span.__exit__(*exc_info) on failure so a setup error does not leave the active span on the scope (see google_genai stream wrappers).
Evidence
new_stream()callssentry_sdk.traces.start_span(...), andSpan._start()immediately sets the span as the current scope span whenactive=True.result = f(self, *args, **kwargs)is unguarded; onlynew_iterator/new_iterator_asynccallspan.__exit__.- If
AgentExecutor.streamraises before returning an iterator, the span is never finished and remains the active span for later work. google_genaistream wrappers catch exceptions around the underlying call and finish the span; this path does not.
Identified by Warden · code-review · GUS-JBC
| @@ -1169,44 +794,7 @@ def capture_event( | |||
|
|
|||
| envelope = Envelope(headers=headers) | |||
|
|
|||
| if is_transaction and isinstance(profile, Profile): | |||
| envelope.add_profile(profile.to_json(event_opt, self.options)) | |||
|
|
|||
| if is_transaction and not span_recorder_has_gen_ai_span: | |||
| envelope.add_transaction(event_opt) | |||
| elif is_transaction: | |||
| split_spans = _split_gen_ai_spans(event_opt) | |||
| if split_spans is None or not split_spans[1]: | |||
| envelope.add_transaction(event_opt) | |||
| else: | |||
| non_gen_ai_spans, gen_ai_spans = split_spans | |||
|
|
|||
| event_opt["spans"] = non_gen_ai_spans | |||
| envelope.add_transaction(event_opt) | |||
|
|
|||
| converted_gen_ai_spans = [ | |||
| _serialized_v1_span_to_serialized_v2_span(span, event_opt) | |||
| for span in gen_ai_spans | |||
| if isinstance(span, dict) | |||
| ] | |||
|
|
|||
| envelope.add_item( | |||
| Item( | |||
| type=SpanBatcher.TYPE, | |||
| content_type=SpanBatcher.CONTENT_TYPE, | |||
| headers={ | |||
| "item_count": len(converted_gen_ai_spans), | |||
| }, | |||
| payload=PayloadRef( | |||
| json={ | |||
| "version": 2, | |||
| "items": converted_gen_ai_spans, | |||
| }, | |||
| ), | |||
| ) | |||
| ) | |||
|
|
|||
| elif is_checkin: | |||
| if is_checkin: | |||
| envelope.add_checkin(event_opt) | |||
| else: | |||
| envelope.add_event(event_opt) | |||
There was a problem hiding this comment.
Transaction events now sent as error events
After removing transaction handling, events with type "transaction" still pass through and are sampled as errors then envelope.add_event()'d; drop or reject them explicitly instead of mis-categorizing.
Evidence
- Previously
is_transactionskipped_should_sample_errorand usedenvelope.add_transaction(event_opt). - That branch is gone; only
is_checkinis special-cased, sotype == "transaction"falls into theelsepath. envelope.add_event()always sets item type to"event", so a transaction payload is emitted as an error event.- Call sites still exist that pass
{"type": "transaction"}intocapture_event(e.g.tests/test_transport.py), andEventtyping still allows"transaction".
Identified by Warden · code-review · 3PJ-F9H
| ca_certs: "Optional[str]" = None, | ||
| propagate_traces: bool = True, | ||
| traces_sample_rate: "Optional[float]" = None, | ||
| trace_lifecycle: "Optional[Literal['static', 'stream']]" = None, | ||
| traces_sampler: "Optional[TracesSampler]" = None, |
There was a problem hiding this comment.
trace_lifecycle removed without migration note
Removing trace_lifecycle will hard-fail existing init(trace_lifecycle=...) callers, and MIGRATION_GUIDE.md does not document the removal or that stream mode is now the only behavior.
Evidence
- The hunk deletes
trace_lifecycle: Optional[Literal['static', 'stream']]from the public constructor signature. DEFAULT_OPTIONSis generated fromClientConstructor.__init__, so the option disappears from accepted config keys._get_options()rejects unknown keys withTypeError, so old configs break at init time.- MIGRATION_GUIDE.md has no entry for
trace_lifecycle, while related removals likestream_gen_ai_spansare explicitly listed.
Identified by Warden · code-review · KAM-DSE
| streaming_span = sentry_sdk.traces.start_span( | ||
| name=span.name, | ||
| parent_span=span, | ||
| attributes={ | ||
| "sentry.op": OP.HTTP_CLIENT_STREAM, | ||
| "sentry.origin": Boto3Integration.origin, | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Boto3 streaming span remains active and hijacks subsequent span parenting
Create the streaming span with active=False (or explicitly scope it only around body reads). The current implementation leaves it as the active span for the entire lifetime of the returned StreamingBody, causing unrelated spans and trace propagation to use the HTTP streaming span as their parent and allowing delayed cleanup to overwrite the current span stack.
Evidence
_sentry_after_call()ends the request span at line 173, then startsstreaming_spanwith the defaultactive=Trueat lines 179-186.Span._start()replacesscope.spanwith the streaming span, so spans created while the body remains open are parented tohttp.client.streamrather than the caller's span.streaming_span.end()runs later fromread()orclose()and restores the span saved at creation; if cleanup occurs while another span is active,_end()unconditionally overwrites that current scope entry, corrupting the span stack.- Existing boto3 streaming tests assert emitted span metadata and parent IDs but do not verify the current span or nested spans while the body is open.
Identified by Warden · find-bugs · L6S-EH8
We're preparing our next major on this branch.
The project is tracked in Linear. If you don't have access, we'll try to tag issues belonging to the project with the
SDK3.0 label on GitHub so that you can follow along.Notable changes
Context
You might have read this announcement about us discontinuing work on a 3.0. This is referring to the work done on the
potel-basebranch, which included two types of changes: a huge refactor of our tracing code on the one hand, and various unrelated changes, improvements and fixes on the other. We're dropping the huge refactor part, and only porting the rest, to a new branch and eventually a new 3.0 release.