These per-SDK skills, plus a handful of setup/workflow skills, are superseded by the sentry-instrument skill and the references/sdks/ trees. Move them under skills-legacy/ so they are excluded from the plugin build and skipped by the drift automation, while skills.sentry.dev can still serve them as a fallback for existing URL consumers. Moves 26 skills: the 19 per-SDK skills plus sentry-sdk-skill-creator, sentry-fix-issues, the three sentry-span-streaming-* skills, sentry-instrument-logging, and sentry-instrumentation-guide. Regenerates SKILL_TREE.md and repaths the moved entries in CODEOWNERS. sentry-sdk is intentionally left in place; its removal is handled in a separate PR.
12 KiB
Tracing — Sentry Python SDK
Minimum SDK:
sentry-sdk0.11.2+ for basic tracing; 2.x forupdate_current_span()and enhanced@trace
Configuration
| Option | Type | Default | Purpose |
|---|---|---|---|
traces_sample_rate |
float |
None |
Fraction of transactions to trace (0.0–1.0); None disables tracing |
traces_sampler |
callable |
None |
Per-transaction sampling function; overrides traces_sample_rate |
trace_propagation_targets |
list |
all origins | URLs to inject sentry-trace / baggage headers into |
functions_to_trace |
list |
[] |
Fully-qualified function names to auto-wrap with spans |
instrumenter |
str |
"sentry" |
Set to "otel" to use OpenTelemetry bridge |
Code Examples
Enable tracing
import sentry_sdk
sentry_sdk.init(
dsn="https://<key>@<org>.ingest.sentry.io/<project>",
traces_sample_rate=1.0, # 1.0 = 100% of transactions; reduce in production
)
Custom spans with context manager
import sentry_sdk
# Top-level transaction
with sentry_sdk.start_transaction(op="task", name="Process Order"):
with sentry_sdk.start_span(name="Validate Items") as span:
span.set_data("item_count", len(items))
validate(items)
with sentry_sdk.start_span(name="Charge Card"):
charge_card(order)
@sentry_sdk.trace decorator
import sentry_sdk
# Simple decorator — uses function name as span name
@sentry_sdk.trace
def process_payment(order_id: str):
...
# With custom op, name, and attributes (SDK 2.35.0+)
@sentry_sdk.trace(op="payment", name="Charge Customer", attributes={"version": 2})
def charge_customer(amount: int):
...
# Static / class methods — decorator order matters!
class PaymentService:
@staticmethod
@sentry_sdk.trace # MUST come AFTER @staticmethod
def process():
...
@classmethod
@sentry_sdk.trace # MUST come AFTER @classmethod
def batch_process(cls):
...
Adding data to spans
import sentry_sdk
with sentry_sdk.start_span(name="db-query") as span:
span.set_data("db.system", "postgresql")
span.set_data("db.table", "orders")
span.set_data("row_count", 42)
span.set_data("cache_hit", False)
# Only primitive types: str, int, float, bool, or homogeneous lists
Accessing the active span/transaction
import sentry_sdk
# Get active span
span = sentry_sdk.get_current_span()
if span:
span.set_data("key", "value")
# Get active transaction
txn = sentry_sdk.get_current_scope().transaction
if txn:
txn.set_tag("order.type", "subscription")
# Update active span in-place (SDK 2.x)
sentry_sdk.update_current_span(
op="http.client",
name="POST /api/charge",
attributes={"http.status_code": 200},
)
Auto-trace multiple functions without decorators
import sentry_sdk
sentry_sdk.init(
dsn="...",
traces_sample_rate=1.0,
functions_to_trace=[
{"qualified_name": "myapp.billing.processor.charge_card"},
{"qualified_name": "myapp.billing.processor.BillingService.validate"},
],
)
# No changes to the functions themselves required
Dynamic sampling with traces_sampler
import sentry_sdk
def traces_sampler(sampling_context):
# Honour parent's sampling decision in distributed traces
parent = sampling_context.get("parent_sampled")
if parent is not None:
return float(parent)
op = sampling_context["transaction_context"].get("op", "")
if op == "http.server":
name = sampling_context["transaction_context"].get("name", "")
if name in ("/health", "/ping", "/readyz"):
return 0 # drop health checks
return 0.5 # sample 50% of HTTP requests
elif op == "task":
return 0.1 # sample 10% of background tasks
else:
return 0.01 # default 1%
sentry_sdk.init(dsn="...", traces_sampler=traces_sampler)
Auto-instrumented frameworks
| Framework | Auto-enabled | What is traced |
|---|---|---|
| Django | ✅ | Requests, DB queries (ORM), cache, signals |
| Flask | ✅ | Requests, Jinja2 rendering |
| FastAPI / Starlette | ✅ | Requests, background tasks |
| Pyramid | ✅ | Requests, route matching |
| Celery | ✅ | Task execution, queue operations |
| SQLAlchemy | ✅ | All queries as spans + breadcrumbs |
| Redis | ✅ | All commands as spans + breadcrumbs |
| PyMongo | ✅ | All queries (covers mongoengine, Motor) |
| requests / httpx / httpx2 | ✅ | Outbound HTTP calls with trace propagation |
Database auto-instrumentation
import sentry_sdk
from sentry_sdk.integrations.redis import RedisIntegration
# SQLAlchemy — auto-enabled, no config needed
sentry_sdk.init(dsn="...", traces_sample_rate=1.0)
# All queries appear as spans automatically
# Redis — with optional cache monitoring
sentry_sdk.init(
dsn="...",
traces_sample_rate=1.0,
send_default_pii=True, # enables full command data in spans
integrations=[
RedisIntegration(
max_data_size=1024, # truncate large values
cache_prefixes=["mycache:", "template."], # appears in cache dashboard
),
],
)
Distributed tracing
Frameworks propagate sentry-trace and baggage headers automatically. For manual HTTP calls:
import sentry_sdk
# Outgoing: inject headers into arbitrary request
headers = {}
headers["sentry-trace"] = sentry_sdk.get_traceparent()
headers["baggage"] = sentry_sdk.get_baggage()
make_request(url="https://internal-api.example.com", headers=headers)
# Incoming: continue a trace from arbitrary headers
incoming_headers = get_headers_from_request()
transaction = sentry_sdk.continue_trace(incoming_headers) # does NOT start it
with sentry_sdk.start_transaction(transaction):
handle_request()
# Browser: inject into HTML meta tags for JS frontend pickup
meta = f'<meta name="sentry-trace" content="{sentry_sdk.get_traceparent()}">'
meta += f'<meta name="baggage" content="{sentry_sdk.get_baggage()}">'
Limit propagation targets
import sentry_sdk, re
sentry_sdk.init(
dsn="...",
traces_sample_rate=1.0,
trace_propagation_targets=[
"localhost",
"https://api.myservice.com",
re.compile(r"^https://internal\."),
],
# Set to [] to disable outgoing propagation entirely
)
OpenTelemetry — OTLP Integration (recommended)
Minimum SDK:
sentry-sdk2.x withsentry-sdk[opentelemetry-otlp]
If the project already uses OpenTelemetry for tracing, use the OTLP integration instead of Sentry's native tracing. Sentry ingests OTel spans directly via its OTLP endpoint — no span conversion, no dual instrumentation.
When to use this path: OTel packages (opentelemetry-sdk, opentelemetry-distro, opentelemetry-instrumentation-*) detected in requirements, or TracerProvider / start_as_current_span found in source.
When NOT to use this path: No OpenTelemetry in the project — use Sentry's native traces_sample_rate instead.
Setup
pip install "sentry-sdk[opentelemetry-otlp]"
import sentry_sdk
from sentry_sdk.integrations.otlp import OTLPIntegration
sentry_sdk.init(
dsn=os.environ["SENTRY_DSN"],
send_default_pii=True,
enable_logs=True,
integrations=[
OTLPIntegration(),
# OTLPIntegration(collector_url="http://localhost:4318/v1/traces"), # set if sending spans to an OTel Collector
],
# Do NOT set traces_sample_rate — tracing is handled by OTel
# Errors, Logs, Crons, and Metrics are still captured natively by Sentry
# and automatically linked to the active OTel trace
)
Configure your OTel instrumentations separately (keep your existing OTel setup):
from opentelemetry.instrumentation.auto_instrumentation import sitecustomize
# or configure manually:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
provider = TracerProvider()
trace.set_tracer_provider(provider)
# Sentry auto-adds its OTLP exporter and propagator —
# no manual SpanProcessor or Propagator wiring needed
How it works
- Sentry derives an OTLP endpoint from your DSN and registers a
SpanExporterwith the existingTracerProvider— your other exporters (Jaeger, Zipkin, Collector) continue working - A propagator injects
sentry-trace+baggageheaders for distributed tracing with other Sentry-instrumented services - Errors captured via
sentry_sdk.capture_exception()are automatically linked to the active OTel trace/span via sharedtrace_id
OTLP configuration options
| Option | Default | Purpose |
|---|---|---|
setup_otlp_traces_exporter |
True |
Auto-configure exporter; set False if you send to your own Collector |
collector_url |
None |
OTLP HTTP endpoint of an OTel Collector (e.g., http://localhost:4318/v1/traces); when set, spans are sent to the collector instead of directly to Sentry |
setup_propagator |
True |
Auto-configure propagator; set False if you manage propagation yourself |
capture_exceptions |
False |
Intercept exceptions recorded via OTel Span.record_exception |
Important
Do not combine OTLP with native Sentry tracing. When using OTLPIntegration:
- Do not set
traces_sample_rateortraces_sampler - Do not set
instrumenter="otel"(that is the legacy SpanProcessor approach) - Do not use
sentry_sdk.start_transaction()orsentry_sdk.start_span()— use OTel'stracer.start_as_current_span()instead
OpenTelemetry bridge (legacy)
Deprecated: Prefer the OTLP integration above. The SpanProcessor bridge below is the older approach.
# pip install "sentry-sdk[opentelemetry]"
import sentry_sdk
from opentelemetry import trace
from opentelemetry.propagate import set_global_textmap
from opentelemetry.sdk.trace import TracerProvider
from sentry_sdk.integrations.opentelemetry import SentrySpanProcessor, SentryPropagator
sentry_sdk.init(
dsn="...",
traces_sample_rate=1.0,
instrumenter="otel", # disables built-in Sentry auto-instrumentation
)
provider = TracerProvider()
provider.add_span_processor(SentrySpanProcessor())
trace.set_tracer_provider(provider)
set_global_textmap(SentryPropagator())
# Use OTel API as normal — spans appear in Sentry
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("my-operation"):
do_work()
Best Practices
- Use
traces_samplerinstead oftraces_sample_ratefor production — it lets you drop health checks, adjust by route, and honour distributed trace decisions - Always finish spans — unfinished spans are silently dropped
- Use
span.set_data()notspan.set_tag()for span-level data (tags are for scope-level filtering) - Set
trace_propagation_targetsto avoid leakingsentry-traceheaders to third-party services - Add
sentry-traceandbaggageto your CORS allowlist when tracing browser-to-backend flows
Troubleshooting
| Issue | Solution |
|---|---|
| No transactions appearing | Verify traces_sample_rate > 0 or traces_sampler returns non-zero |
| Spans not linked to transaction | Ensure spans are created inside an active start_transaction() context |
| Distributed traces broken | Check that sentry-trace and baggage headers pass through proxies/gateways |
description= deprecation warning |
Replace description= with name= in start_span() calls |
| OTel spans not appearing (OTLP) | Verify sentry-sdk[opentelemetry-otlp] is installed; do not set traces_sample_rate when using OTLPIntegration |
| OTel spans not appearing (legacy bridge) | Ensure SentrySpanProcessor is added to TracerProvider before creating spans |
@sentry_sdk.trace on static method fails |
Put @sentry_sdk.trace AFTER @staticmethod / @classmethod |