with one click
opentelemetry-python-manual-span
// Create and manipulate OpenTelemetry spans manually in Python — nested spans, attributes, semantic conventions, events, links, status, and exception recording.
// Create and manipulate OpenTelemetry spans manually in Python — nested spans, attributes, semantic conventions, events, links, status, and exception recording.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | opentelemetry-python-manual-span |
| description | Create and manipulate OpenTelemetry spans manually in Python — nested spans, attributes, semantic conventions, events, links, status, and exception recording. |
| tech_stack | ["opentelemetry"] |
| language | ["python"] |
| capability | ["observability"] |
| version | OpenTelemetry Python (Traces Stable) |
| collected_at | "2025-12-03T00:00:00.000Z" |
Source: https://opentelemetry.io/docs/languages/python/instrumentation/, https://opentelemetry-python.readthedocs.io/en/latest/sdk/index.html
Create, nest, enrich, and finalize spans by hand in Python applications. This skill covers every span-level API: starting spans as context managers or bare objects, decorating functions, attaching attributes (including semantic conventions), recording timed events, establishing causal links between non-nested spans, setting error status, and capturing exceptions. Assumes a tracer instance is already available via trace.get_tracer().
# Pattern A: context manager (auto-close, becomes current span)
with tracer.start_as_current_span("operation") as span:
span.set_attribute("key", "value")
# span is the current span here; auto-closes on block exit
# Pattern B: bare span (manual close, does NOT become current)
span = tracer.start_span("background-task")
try:
do_work()
finally:
span.end()
Prefer Pattern A for all synchronous call chains. Use Pattern B when the span lifetime doesn't map to a single scope (concurrent tasks, fire-and-forget).
from opentelemetry.trace import Status, StatusCode
with tracer.start_as_current_span("risky-op") as span:
try:
result = risky_call()
span.add_event("call succeeded")
return result
except Exception as ex:
span.set_status(Status(StatusCode.ERROR))
span.record_exception(ex)
raise
Always pair set_status(ERROR) with record_exception(ex) — the status alone carries no stack trace or exception message.
# pip install opentelemetry-semantic-conventions
from opentelemetry.semconv.trace import SpanAttributes
span.set_attribute(SpanAttributes.HTTP_METHOD, "GET")
span.set_attribute(SpanAttributes.HTTP_URL, "https://api.example.com/v1/items")
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, 200)
| Method | Behavior |
|---|---|
tracer.start_as_current_span(name, attributes=..., links=...) | Context manager; sets as current; auto-ends on exit |
tracer.start_span(name, attributes=..., links=...) | Bare span; NOT current; caller must .end() |
@tracer.start_as_current_span("name") | Decorator wrapping entire function |
| Method | Purpose |
|---|---|
span.set_attribute(k, v) | Key/value — str, int, float, bool, or homogeneous list thereof |
span.add_event("msg", attributes=...) | Timestamped human-readable event |
span.set_status(Status(StatusCode.ERROR)) | Mark span as errored |
span.record_exception(ex) | Record exception with traceback |
span.get_span_context() | Get immutable SpanContext for link creation |
link = trace.Link(some_span.get_span_context(), attributes={"relationship": "causal"})
with tracer.start_as_current_span("consumer", links=[link]):
...
Links create causal associations without parent-child nesting — useful for async message passing where the producer and consumer have independent lifetimes.
| Code | Meaning |
|---|---|
StatusCode.UNSET | Default — completed without error |
StatusCode.OK | Explicit success (rarely needed) |
StatusCode.ERROR | Failed operation |
start_span() → no current span: If downstream code calls trace.get_current_span(), it will NOT see a span created with start_span(). Always use start_as_current_span() for synchronous call chains..end() is mandatory with start_span(): Forgetting to call span.end() leaks the span and it will never be exported. Wrap in try/finally.set_status(ERROR) without record_exception(ex) gives no visibility into why it failed. Always record the exception too.@tracer.start_as_current_span is evaluated at import time. If tracer is a local variable inside a function, the decorator will fail with NameError.pip install opentelemetry-semantic-conventions; import from opentelemetry.semconv.trace.trace_id and span_id are fixed at span creation. Plan your trace topology before creating spans — you can't retroactively reparent.is_recording() may return False: If the span was sampled out, calling set_attribute or add_event is a no-op. Check span.is_recording() before expensive attribute computation.opentelemetry-python-sdk: A global TracerProvider must be set and a tracer acquired before any span API is usable.trace.get_current_span() inside route handlers to enrich spans that FastAPIInstrumentor created automatically.BatchSpanProcessor → OTLPSpanExporter chain configured by opentelemetry-python-otlp.opentelemetry-python-propagation), use tracer.start_as_current_span(name, context=extracted_ctx) to attach the span to the propagated context.