@observe decorator handles span creation automatically. But sometimes you need direct control:
- Conditional tracing — Start a span only if certain conditions are met.
- Dynamic naming — Determine the span name based on runtime data.
- Long-running operations — Hold a span open across multiple function calls.
- Non-function boundaries — Trace a block of code that isn’t a function.
Choosing the Right Method
It’s important to distinguish:- Creating a span (starts timing) vs activating a span (makes it the current parent for nesting)
- Active spans (recommended) vs detached/manual spans (advanced)
- Active span: create a folder and open it — new files go inside automatically.
- Detached span: create a folder but don’t open it — new files won’t go inside unless you explicitly place them there.
Span Lifecycle
A manual span follows three steps:- Start — Create the span with a name. It becomes the “current” span, and any child spans nest under it.
- Enrich — Add attributes, set input/output, record events or errors.
- End — Close the span. This records the end time and sends the span to Laminar.
with in Python) or try/finally blocks to ensure spans always close, even when errors occur.
Common Options (and When to Use Them)
- TypeScript
- Python
In the Laminar UI
- Manually-created spans look the same as auto-instrumented spans: a node in the trace tree with timing and attributes.
- If a span is active, any work executed inside it is parented underneath it automatically.
Span Types
Spans can have a type (DEFAULT, LLM, TOOL, and a few others) that changes how they render in the transcript view and how they’re aggregated. Pass spanType / span_type at span creation. See Span Types for when to use each, and LLM Cost Tracking for the attributes LLM spans expect.