> ## Documentation Index
> Fetch the complete documentation index at: https://tracevell.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Trace Parts of Your Code

A span represents a unit of work. It has a name, a start time, an end time, and optional attributes. Every trace is a tree of spans.

Most of the time, the `@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 spans** automatically parent anything that runs inside them (including instrumented third-party libraries), because context propagation can attach child spans under the active span.

**Detached/manual spans** are useful when you need to pass a span object around explicitly (or across async boundaries where call-stack parenting isn’t enough).

Analogy:

* 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:

1. **Start** — Create the span with a name. It becomes the "current" span, and any child spans nest under it.
2. **Enrich** — Add attributes, set input/output, record events or errors.
3. **End** — Close the span. This records the end time and sends the span to Tracevell.

If you don't end a span, it won't appear in your traces. Use context managers (`with` in Python) or `try/finally` blocks to ensure spans always close, even when errors occur.

## Common Options (and When to Use Them)

> [tab] TypeScript
>
> | Method | Creates span | Activates span | Ends span | Use when |
> | - | - | - | - | - |
> | `observe()` | ✅ | ✅ | ✅ | You’re tracing a function or request handler |
> | `Laminar.startActiveSpan()` | ✅ | ✅ | ❌ | You need a parent span outside `observe()` |
> | `Laminar.startSpan()` | ✅ | ❌ | ❌ | You want a detached span you’ll pass around |
> | `Laminar.withSpan()` | ❌ | ✅ | ❌ (optional) | You have a span object and want it to be current |
>
> ```typescript theme={null}
> import { Tracevell } from '@lmnr-ai/lmnr';
>
> const span = Laminar.startActiveSpan({ name: 'custom_operation' });
> try {
>   // Any spans created here become children of custom_operation
>   await doWork();
> } catch (error) {
>   span.recordException(error as Error);
>   throw error;
> } finally {
>   span.end();
> }
> ```
>
> See also: [`Laminar.startActiveSpan`](https://tracevell.com/docs/sdk/manual-spans#ts-tracevell-start-span-block)

> [tab] Python
>
> | Method | Creates span | Activates span | Ends span | Use when |
> | - | - | - | - | - |
> | `Laminar.start_as_current_span()` | ✅ | ✅ | ✅ | Default choice for tracing a block |
> | `Laminar.start_active_span()` | ✅ | ✅ | ❌ | You want a span object but still need nesting |
> | `Laminar.start_span()` | ✅ | ❌ | ❌ | You want a detached span you’ll pass around |
> | `use_span()` | ❌ | ✅ | ❌ (optional) | You have a span and want it to be current |
>
> ```python theme={null}
> from lmnr import Tracevell
>
> with Laminar.start_as_current_span(name="custom_operation") as span:
>     try:
>         do_work()
>     except Exception as e:
>         span.record_exception(e)
>         raise
>     # Span ends automatically on exit
> ```
>
> > [!WARNING]
> >
> > In Python, prefer `start_as_current_span()` (context manager) or `start_span()` + `use_span()`.
> > If you use `start_active_span()`, make sure you end the span in the same thread/async context it was created in—otherwise span hierarchy can become corrupted.
>
> See also: [`Laminar.start_as_current_span`](https://tracevell.com/docs/sdk/manual-spans#py-tracevell-start-span-block)

## In the Tracevell 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](https://tracevell.com/docs/tracing/structure/span-types.md) for when to use each, and [LLM cost tracking](https://tracevell.com/docs/tracing/cost-tracking.md) for the attributes `LLM` spans expect.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.