# `LangChain.OpenTelemetry.SpanHandler`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/open_telemetry/span_handler.ex#L4)

Telemetry handler that creates OpenTelemetry spans from LangChain telemetry events.

Spans follow a subset of the GenAI Semantic Conventions (v1.40+) — see
`LangChain.OpenTelemetry.Attributes` for the attributes that are emitted:

| Telemetry Event                       | OTel Span Name              | Kind       | `gen_ai.operation.name` |
|---------------------------------------|-----------------------------|------------|------------------------|
| `[:langchain, :chain, :execute, ...]` | `invoke_agent llm_chain`    | `:internal` | `invoke_agent`          |
| `[:langchain, :llm, :call, ...]`      | `chat {model}`              | `:client`  | `chat`                 |
| `[:langchain, :tool, :call, ...]`     | `execute_tool {tool_name}`  | `:internal` | `execute_tool`          |

Span hierarchy is automatic for synchronous work: chain → LLM call → tool call
run in the same process, so parent context propagation works via the process
dictionary.

## Attribute inheritance

Attributes a caller supplies through `custom_context[:otel_attributes]` (see
`LangChain.OpenTelemetry.Attributes.passthrough_attributes/1`), along with
`gen_ai.conversation.id`, are stashed on the OpenTelemetry context when the chain
span opens, so the `chat` and `execute_tool` spans nested under it carry them too.
Without this they would land on the chain span only, and a backend could group
*traces* by tenant or session but not filter *spans* by them.

This matters most for the `chat` span, which is the one span that cannot reach
`custom_context` at all: its telemetry metadata is built inside
`LangChain.ChatModels.ChatModel.llm_telemetry_span/3`, called from a provider's
`call/3`, and that callback takes no context argument.

Two properties worth knowing:

  * **Inherited attributes always lose** to attributes the span derives itself, so
    an inherited value can never mask a real one (notably `gen_ai.request.model`).
  * The stash rides the OpenTelemetry **context**, not baggage, so it follows the
    trace across process boundaries — including `async: true` tools — but is never
    serialized onto outbound requests. It is also unwound by the same
    `OpenTelemetry.Ctx.detach/1` that ends the span, so there is no extra
    lifecycle to leak.

Disable with `inherit_attributes: false` (see `LangChain.OpenTelemetry.Config`).

> #### Async tools {: .info}
>
> Tools declared with `async: true` execute in a separate `Task` process, and
> the OpenTelemetry context is **not** inherited across a process boundary. For
> tools run by `LLMChain`'s built-in executor this is handled automatically —
> the chain captures the current context before spawning each async tool and
> re-attaches it inside the `Task`, so async tool spans nest under the chain
> span. If you spawn your own processes running LangChain operations, do the
> same yourself: capture `OpenTelemetry.Ctx.get_current/0` before spawning and
> `OpenTelemetry.Ctx.attach/1` inside the process (e.g. via the
> `:on_tool_pre_execution` callback).

## Usage

This module is used internally by `LangChain.OpenTelemetry.setup/1`. You
typically don't need to interact with it directly.

# `ctx_attributes_key`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/open_telemetry/span_handler.ex#L85)

```elixir
@spec ctx_attributes_key() :: term()
```

Returns the OpenTelemetry context key used to carry inherited span attributes.

Exposed for `LangChain.OpenTelemetry.Enrich`, which lets a host seed the same
attributes from outside a chain run.

# `events`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/open_telemetry/span_handler.ex#L91)

```elixir
@spec events() :: [[atom()]]
```

Returns the list of telemetry events this handler attaches to.

# `handle_event`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/open_telemetry/span_handler.ex#L117)

```elixir
@spec handle_event([atom()], map(), map(), LangChain.OpenTelemetry.Config.t()) :: :ok
```

Telemetry handler callback. Dispatches to the appropriate handler based on the event.

# `handler_id`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/open_telemetry/span_handler.ex#L111)

```elixir
@spec handler_id() :: String.t()
```

Returns the telemetry handler ID prefix used for attaching/detaching.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
