Skip to content

[do not merge] feat: Span streaming & new span API#5551

Draft
sentrivana wants to merge 149 commits intomasterfrom
feat/span-first
Draft

[do not merge] feat: Span streaming & new span API#5551
sentrivana wants to merge 149 commits intomasterfrom
feat/span-first

Conversation

@sentrivana
Copy link
Contributor

Introduce a new start_span() API with a simpler and more intuitive signature to eventually replace the original start_span() and start_transaction() APIs.

Additionally, introduce a new streaming mode (sentry_sdk.init(_experiments={"trace_lifecycle": "stream"})) that will send spans as they finish, rather than by transaction.

import sentry_sdk

sentry_sdk.init(
    _experiments={"trace_lifecycle": "stream"},
)

with sentry_sdk.traces.start_span(name="my_span"):
    ...

The new API MUST be used with the new streaming mode, and the old API MUST be used in the legacy non-streaming (static) mode.

Migration guide: getsentry/sentry-docs#16072

Notes

  • The diff is huge mostly because I've optimized for easy removal of legacy code in the next major, deliberately duplicating a lot. I'll of course split it up to reviewable PRs once ready.
    • Chose to go with a new file and a new span class so that we can just remove the old Span and drop the new StreamedSpan in tracing.py as a replacement.
  • The batcher for spans is a bit different from the logs and metrics batchers because it needs to batch by trace_id (we can't send spans from different traces in the same envelope).

Release Plan

  • There will be prereleases for internal testing.
  • We'll release the new API in a minor version as opt-in.
  • In the next major, we'll drop the legacy API.

Project

https://linear.app/getsentry/project/span-first-sdk-python-727da28dd037/overview

@sentrivana sentrivana changed the title Feat/span first [do not merge] feat: Span streaming & new span API Feb 26, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 26, 2026

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


This PR will not appear in the changelog.


🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 26, 2026

Codecov Results 📊

13 passed | Total: 13 | Pass Rate: 100% | Execution Time: 4.65s

All tests are passing successfully.

❌ Patch coverage is 22.65%. Project has 14385 uncovered lines.

Files with missing lines (180)
File Patch % Lines
langchain.py 3.28% ⚠️ 590 Missing
utils.py 52.63% ⚠️ 432 Missing and 79 partials
openai.py 5.38% ⚠️ 510 Missing
utils.py 0.00% ⚠️ 479 Missing
tracing_utils.py 39.24% ⚠️ 429 Missing and 28 partials
__init__.py 5.28% ⚠️ 377 Missing
starlette.py 5.26% ⚠️ 360 Missing
scope.py 67.05% ⚠️ 289 Missing and 69 partials
transport.py 23.54% ⚠️ 302 Missing and 2 partials
client.py 55.38% ⚠️ 224 Missing and 59 partials
mcp.py 5.24% ⚠️ 253 Missing
anthropic.py 9.03% ⚠️ 252 Missing
traces.py 27.14% ⚠️ 247 Missing
strawberry.py 7.76% ⚠️ 226 Missing
transaction_profiler.py 35.52% ⚠️ 216 Missing and 10 partials
utils.py 16.35% ⚠️ 220 Missing
langgraph.py 5.29% ⚠️ 215 Missing
span_processor.py 0.00% ⚠️ 205 Missing
continuous_profiler.py 43.45% ⚠️ 177 Missing and 17 partials
tracing.py 71.16% ⚠️ 137 Missing and 39 partials
huggingface_hub.py 9.42% ⚠️ 173 Missing
__init__.py 6.01% ⚠️ 172 Missing
aws_lambda.py 16.50% ⚠️ 167 Missing
rust_tracing.py 0.00% ⚠️ 163 Missing
sanic.py 9.60% ⚠️ 160 Missing
aiohttp.py 10.84% ⚠️ 148 Missing
cloud_resource_context.py 0.00% ⚠️ 145 Missing
ai_client.py 0.00% ⚠️ 145 Missing
litellm.py 0.00% ⚠️ 138 Missing
litestar.py 9.59% ⚠️ 132 Missing
starlite.py 8.33% ⚠️ 132 Missing
asgi.py 19.38% ⚠️ 129 Missing
falcon.py 8.94% ⚠️ 112 Missing
flask.py 12.50% ⚠️ 112 Missing
spotlight.py 28.47% ⚠️ 103 Missing and 8 partials
cohere.py 12.70% ⚠️ 110 Missing
asgi.py 0.00% ⚠️ 109 Missing
envelope.py 54.04% ⚠️ 91 Missing and 17 partials
hub.py 45.60% ⚠️ 105 Missing and 3 partials
pymongo.py 10.17% ⚠️ 106 Missing
caching.py 0.00% ⚠️ 106 Missing
asyncpg.py 11.86% ⚠️ 104 Missing
utils.py 0.00% ⚠️ 103 Missing
stdlib.py 53.51% ⚠️ 86 Missing and 15 partials
templates.py 0.00% ⚠️ 100 Missing
utils.py 13.79% ⚠️ 100 Missing
quart.py 16.10% ⚠️ 99 Missing
wsgi.py 22.66% ⚠️ 99 Missing
gcp.py 0.00% ⚠️ 98 Missing
otlp.py 0.00% ⚠️ 97 Missing
agent_run.py 0.00% ⚠️ 97 Missing
sessions.py 27.82% ⚠️ 96 Missing
models.py 4.95% ⚠️ 96 Missing
httpx.py 12.15% ⚠️ 94 Missing
pyramid.py 13.76% ⚠️ 94 Missing
tornado.py 14.55% ⚠️ 94 Missing
__init__.py 0.00% ⚠️ 93 Missing
bottle.py 11.65% ⚠️ 91 Missing
middleware.py 0.00% ⚠️ 90 Missing
agent_run.py 0.00% ⚠️ 90 Missing
_wsgi_common.py 30.71% ⚠️ 88 Missing and 1 partials
tools.py 0.00% ⚠️ 88 Missing
runner.py 0.00% ⚠️ 87 Missing
loguru.py 11.58% ⚠️ 84 Missing
__init__.py 4.65% ⚠️ 82 Missing
asyncio.py 0.00% ⚠️ 80 Missing
invoke_agent.py 0.00% ⚠️ 79 Missing
graphene.py 13.48% ⚠️ 77 Missing
session.py 15.56% ⚠️ 76 Missing
clickhouse_driver.py 17.58% ⚠️ 75 Missing
sqlalchemy.py 10.71% ⚠️ 75 Missing
pure_eval.py 0.00% ⚠️ 73 Missing
worker.py 22.58% ⚠️ 72 Missing
monitoring.py 17.44% ⚠️ 71 Missing
ariadne.py 14.46% ⚠️ 71 Missing
gql.py 10.13% ⚠️ 71 Missing
fastapi.py 15.85% ⚠️ 69 Missing
transactions.py 0.00% ⚠️ 67 Missing
_queue.py 26.67% ⚠️ 66 Missing
logging.py 66.67% ⚠️ 51 Missing and 15 partials
boto3.py 14.86% ⚠️ 63 Missing
streaming.py 0.00% ⚠️ 62 Missing
__init__.py 86.43% ⚠️ 38 Missing and 23 partials
chalice.py 16.18% ⚠️ 57 Missing
propagator.py 0.00% ⚠️ 57 Missing
api.py 63.40% ⚠️ 56 Missing
server.py 0.00% ⚠️ 56 Missing
spark_driver.py 67.47% ⚠️ 54 Missing and 2 partials
graph_nodes.py 0.00% ⚠️ 52 Missing
gnu_backtrace.py 0.00% ⚠️ 51 Missing
socket.py 0.00% ⚠️ 50 Missing
views.py 0.00% ⚠️ 50 Missing
_span_batcher.py 31.94% ⚠️ 49 Missing
_batcher.py 41.25% ⚠️ 47 Missing
serializer.py 82.26% ⚠️ 33 Missing and 13 partials
invoke_agent.py 0.00% ⚠️ 46 Missing
_asgi_common.py 16.67% ⚠️ 45 Missing
signals_handlers.py 0.00% ⚠️ 44 Missing
utils.py 22.22% ⚠️ 42 Missing and 1 partials
threading.py 63.16% ⚠️ 35 Missing and 5 partials
client.py 0.00% ⚠️ 40 Missing
utils.py 68.54% ⚠️ 28 Missing and 11 partials
executing.py 0.00% ⚠️ 38 Missing
client.py 0.00% ⚠️ 38 Missing
serverless.py 0.00% ⚠️ 36 Missing
caches.py 47.62% ⚠️ 33 Missing and 2 partials
server.py 0.00% ⚠️ 34 Missing
sys_exit.py 0.00% ⚠️ 32 Missing
launchdarkly.py 0.00% ⚠️ 31 Missing
trytond.py 0.00% ⚠️ 30 Missing
integration.py 0.00% ⚠️ 30 Missing
error_tracing.py 0.00% ⚠️ 29 Missing
tools.py 0.00% ⚠️ 27 Missing
ai_client.py 13.33% ⚠️ 26 Missing
execute_tool.py 0.00% ⚠️ 26 Missing
scrubber.py 76.81% ⚠️ 16 Missing and 9 partials
_openai_completions_api.py 19.35% ⚠️ 25 Missing
ray.py 70.24% ⚠️ 25 Missing
_werkzeug.py 11.11% ⚠️ 24 Missing
typer.py 0.00% ⚠️ 24 Missing
tasks.py 0.00% ⚠️ 24 Missing
_async_common.py 75.36% ⚠️ 17 Missing and 7 partials
_compat.py 41.03% ⚠️ 23 Missing
statsig.py 0.00% ⚠️ 23 Missing
_sync_common.py 76.06% ⚠️ 17 Missing and 6 partials
logger.py 43.59% ⚠️ 22 Missing
decorator.py 37.14% ⚠️ 22 Missing
__init__.py 84.78% ⚠️ 14 Missing and 8 partials
_log_batcher.py 0.00% ⚠️ 21 Missing
attachments.py 27.59% ⚠️ 21 Missing
unraisablehook.py 0.00% ⚠️ 21 Missing
openfeature.py 0.00% ⚠️ 20 Missing
execute_tool.py 0.00% ⚠️ 20 Missing
dramatiq.py 90.91% ⚠️ 10 Missing and 9 partials
unleash.py 0.00% ⚠️ 19 Missing
_lru_cache.py 43.33% ⚠️ 17 Missing and 1 partials
model_request.py 0.00% ⚠️ 18 Missing
monitor.py 81.97% ⚠️ 11 Missing and 5 partials
beat.py 92.24% ⚠️ 9 Missing and 7 partials
utils.py 0.00% ⚠️ 16 Missing
redis_cluster.py 52.94% ⚠️ 16 Missing
atexit.py 56.25% ⚠️ 14 Missing and 1 partials
excepthook.py 56.25% ⚠️ 14 Missing and 1 partials
feature_flags.py 57.58% ⚠️ 14 Missing
__init__.py 27.78% ⚠️ 13 Missing
_types.py 60.00% ⚠️ 12 Missing
types.py 0.00% ⚠️ 12 Missing
rq.py 94.12% ⚠️ 5 Missing and 6 partials
handoff.py 0.00% ⚠️ 10 Missing
spark_worker.py 89.29% ⚠️ 6 Missing and 4 partials
metrics.py 47.06% ⚠️ 9 Missing
beam.py 94.05% ⚠️ 5 Missing and 3 partials
dedupe.py 85.00% ⚠️ 6 Missing and 2 partials
_metrics_batcher.py 65.00% ⚠️ 7 Missing
consts.py 0.00% ⚠️ 7 Missing
redis_py_cluster_legacy.py 53.33% ⚠️ 7 Missing
queries.py 88.57% ⚠️ 4 Missing and 3 partials
_init_implementation.py 75.00% ⚠️ 6 Missing
_openai_responses_api.py 33.33% ⚠️ 6 Missing
arq.py 97.52% ⚠️ 3 Missing and 3 partials
debug.py 91.30% ⚠️ 2 Missing and 2 partials
consts.py 0.00% ⚠️ 4 Missing
__init__.py 20.00% ⚠️ 4 Missing
__init__.py 0.00% ⚠️ 4 Missing
__init__.py 89.66% ⚠️ 3 Missing and 1 partials
__init__.py 0.00% ⚠️ 3 Missing
__init__.py 40.00% ⚠️ 3 Missing
__init__.py 0.00% ⚠️ 3 Missing
__init__.py 0.00% ⚠️ 3 Missing
rb.py 70.00% ⚠️ 3 Missing
redis.py 85.00% ⚠️ 3 Missing
consts.py 99.43% ⚠️ 2 Missing
argv.py 100.00% ⚠️ 2 partials
huey.py 100.00% ⚠️ 2 partials
modules.py 94.12% ⚠️ 1 Missing and 1 partials
utils.py 88.24% ⚠️ 2 Missing
agent_workflow.py 71.43% ⚠️ 2 Missing
api.py 100.00% ⚠️ 1 partials
consts.py 0.00% ⚠️ 1 Missing
consts.py 0.00% ⚠️ 1 Missing

Generated by Codecov Action

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition causes span loss when buffer is at flush threshold (sentry_sdk/_span_batcher.py:19)

When MAX_BEFORE_FLUSH (1000) equals MAX_BEFORE_DROP (1000), a race condition exists where spans are unnecessarily dropped. After the 1000th span triggers a flush and releases the lock, subsequent add() calls can acquire the lock before the flush thread clears the buffer, seeing size >= MAX_BEFORE_DROP and dropping spans. This results in data loss during high-throughput scenarios.

Async Redis spans are not closed when exceptions occur (sentry_sdk/integrations/redis/_async_common.py:135)

In _sentry_execute_command, spans are created via __enter__() but __exit__() is called outside of a try/finally block. If old_execute_command raises an exception, the db_span and cache_span will never be closed, causing span leaks. The sync version in _sync_common.py correctly wraps this in a try/finally block (lines 141-151).

AttributeError when legacy Span is on scope during streaming mode (sentry_sdk/scope.py:1249)

At line 1249, parent_span is assigned from self.span or self.get_current_scope().span, which can be a legacy Span (from sentry_sdk.tracing). However, at line 1284, the code accesses parent_span.segment, an attribute that only exists on StreamedSpan, not on the legacy Span class. If streaming mode is enabled but a legacy Span ends up on the scope (e.g., from a third-party integration or mixed code), this will cause an AttributeError: 'Span' object has no attribute 'segment'.

Span silently dropped when end() called without start() (sentry_sdk/traces.py:341)

When span.end() is called without first calling span.start() or using the context manager, the _context_manager_state attribute is not initialized. The code at line 342 attempts to unpack this attribute, and the resulting AttributeError is swallowed by capture_internal_exceptions(). The span is silently dropped without any warning to the user, and the scope's span reference is not restored.

Identified by Warden find-bugs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants