Order the lifecycle of one OTel span from start to export
- 1The Exporter ships the batch to the backend (Langfuse, Phoenix, OTel Collector)
- 2If the work fails, the app sets span status to ERROR and records the exception
- 3The SpanProcessor (typically BatchSpanProcessor) buffers the span
- 4span.end() stamps the end timestamp and hands the span to the SpanProcessor
- 5Application code sets attributes and records events on the span
- 6tracer.start_span(name) creates the span and stores it in the active context
Start, enrich with attributes, mark failure if any, end, buffer through the SpanProcessor, export to the backend.
Picture writing a postcard from a trip. First you grab a blank card and write the date on top. As the day unfolds you jot down details: where you went, who you met, what you ate. If anything went wrong (you missed the train) you scribble a note about that too. At the end of the day you stamp the finish time and drop the card into a mailbox at your hotel. The hotel collects all the cards from all the guests, bundles them together overnight, and the postal service trucks them off to wherever they need to go. The postcard is the span. The mailbox is the SpanProcessor. The truck is the Exporter. Once you drop the card in, you cannot edit it any more.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Understanding the span lifecycle in order is what separates 'I added OTel and traces appear' from 'I can debug why a trace did not appear'. Almost every operational bug in LLM tracing maps to one specific stage of the lifecycle going wrong, and the symptoms differ depending on which stage.
This question puts the six stages on the table. The deeper learning is how the stages interact with async, with short-lived processes, and with the SpanProcessor's batch tuning.
Start: span creation and context
tracer.start_span(name) creates a span object, captures the start timestamp, and assigns it a span id linked to the current trace id (or starts a new trace if no parent context exists). The span is now mutable.
The critical detail is whether the span becomes the active span in the current context. start_span alone does NOT do this; it just creates the object. To make the new span the parent for any subsequent spans, you either use start_as_current_span (a context manager that handles activation and deactivation) or you manually attach it to the context via trace.use_span(span).
The failure mode for getting this wrong is flat traces: every span ends up at the root, none nest, the tree view in your backend looks like a row of bars instead of a hierarchy. This is one of the top two beginner bugs in OTel instrumentation, alongside forgetting to flush at process exit.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
Real products, models, and research that use this idea.
- Python opentelemetry-sdk 1.x BatchSpanProcessor with OTLPSpanExporter is the canonical production pairing.
- Langfuse's OTel ingest endpoint accepts spans exported via OTLP HTTP from BatchSpanProcessor.
What an interviewer would ask next. Try answering before peeking at the approach.
QWhat changes about this lifecycle in an async / coroutine context?
Talk about context propagation across await points and the contextvars-based active span mechanism.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Trying to set attributes or status after calling span.end(). Once end() runs, the span is frozen and any later mutation is silently dropped.
60 second bullets to scan on the way to the call.
Recall the six stages of a span lifecycle in order
Place exception recording and status mutation correctly relative to end()
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.