> ## Documentation Index
> Fetch the complete documentation index at: https://laminar.sh/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 Laminar.

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)

<Tabs items={['TypeScript', 'Python']}>
  <Tab title="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 { Laminar } 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`](/sdk/manual-spans#ts-laminar-start-span-block)
  </Tab>

  <Tab title="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 Laminar

    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.
    </Warning>

    See also: [`Laminar.start_as_current_span`](/sdk/manual-spans#py-laminar-start-span-block)
  </Tab>
</Tabs>

## 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](/tracing/structure/span-types) for when to use each, and [LLM Cost Tracking](/tracing/structure/llm-cost-tracking) for the attributes `LLM` spans expect.
