# `ClaudeWrapper.Telemetry`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/telemetry.ex#L1)

`:telemetry` events emitted by ClaudeWrapper.

Wraps the core exec paths with `:telemetry.span/3` so downstream
applications can subscribe once via `:telemetry.attach_many/4` and
observe query, stream, and session-turn lifecycle.

## Events

  * `[:claude_wrapper, :exec, :start | :stop | :exception]` --
    emitted around `ClaudeWrapper.Query.execute/2` (one-shot,
    synchronous query).

  * `[:claude_wrapper, :stream, :start | :stop | :exception]` --
    emitted around `ClaudeWrapper.Query.stream/2`. The `:stop`
    event fires when the returned stream is fully consumed or
    closed; `:start` fires when the stream is first reduced.

  * `[:claude_wrapper, :session, :turn, :start | :stop | :exception]` --
    emitted around `ClaudeWrapper.Session.send/3` (a single turn
    of a multi-turn session).

  * `[:claude_wrapper, :duplex, :session, :start | :stop]` --
    emitted around a `ClaudeWrapper.DuplexSession` process lifetime
    (`:start` on a successful open, `:stop` on terminate).

  * `[:claude_wrapper, :duplex, :turn, :start | :stop | :exception]` --
    emitted around a single `ClaudeWrapper.DuplexSession.send/3` turn.
    A turn spans several GenServer messages, so these are a manual
    start/stop pair (like the stream lifecycle): `:start` on send,
    `:stop` on the terminal `result`, `:exception` if the turn fails
    before a result (e.g. the subprocess exits mid-turn).

## Measurements

Automatically populated by `:telemetry.span/3`:

  * `:start` -- `%{monotonic_time: integer(), system_time: integer()}`
  * `:stop` -- `%{monotonic_time: integer(), duration: integer()}`
  * `:exception` -- `%{monotonic_time: integer(), duration: integer()}`

## Metadata

Every event carries at least:

  * `:command` -- atom identifying the call site, e.g. `:query`
    (one-shot execute), `:stream`, or `:session_turn`.
  * `:session_id` -- present when known (a resumed session, or the
    `session_id` returned by the CLI in the result).
  * `:model` -- from `ClaudeWrapper.Query.model/2` or session
    `query_opts`.
  * `:resume?` -- boolean, whether this invocation threads a
    previously established `session_id`.

`:stop` events additionally include:

  * `:cost_usd` -- parsed from the final NDJSON result, when
    available.
  * `:exit_code` -- `0` on success, non-zero when the CLI exited
    with an error that still produced output, `nil` for other
    errors.

`:exception` events follow the standard `:telemetry.span/3` shape
and add `:kind`, `:reason`, and `:stacktrace`.

## Example

    :telemetry.attach_many(
      "my-handler",
      [
        [:claude_wrapper, :exec, :stop],
        [:claude_wrapper, :stream, :stop],
        [:claude_wrapper, :session, :turn, :stop]
      ],
      fn event, measurements, metadata, _config ->
        Logger.info(fn ->
          "#{inspect(event)} duration=#{measurements.duration} cost=#{inspect(metadata.cost_usd)}"
        end)
      end,
      nil
    )

# `metadata`

```elixir
@type metadata() :: %{optional(atom()) =&gt; term()}
```

# `duplex_session_start`

```elixir
@spec duplex_session_start(metadata()) :: integer()
```

Emit `[:claude_wrapper, :duplex, :session, :start]` and return the start
monotonic time, to pass to `duplex_session_stop/2`.

# `duplex_session_stop`

```elixir
@spec duplex_session_stop(integer() | nil, metadata()) :: :ok
```

Emit `[:claude_wrapper, :duplex, :session, :stop]` for a started session.

# `duplex_turn_exception`

```elixir
@spec duplex_turn_exception({integer(), metadata()} | nil, term()) :: :ok
```

Emit `[:claude_wrapper, :duplex, :turn, :exception]` for a turn that failed before a result.

# `duplex_turn_start`

```elixir
@spec duplex_turn_start(metadata()) :: {integer(), metadata()}
```

Emit `[:claude_wrapper, :duplex, :turn, :start]` and return a span context
(`{monotonic, metadata}`) to pass to `duplex_turn_stop/2` or
`duplex_turn_exception/2`. A turn spans several GenServer messages, so it uses
this manual pair rather than `:telemetry.span/3`.

# `duplex_turn_stop`

```elixir
@spec duplex_turn_stop({integer(), metadata()} | nil, metadata()) :: :ok
```

Emit `[:claude_wrapper, :duplex, :turn, :stop]` for a started turn span.

# `span_exec`

```elixir
@spec span_exec(ClaudeWrapper.Query.t(), (-&gt; {:ok, ClaudeWrapper.Result.t()}
                                       | {:error, term()})) ::
  {:ok, ClaudeWrapper.Result.t()} | {:error, term()}
```

Wrap a one-shot query execution in a `[:claude_wrapper, :exec, _]` span.

`fun` must return `{:ok, %Result{}} | {:error, term()}`. Stop
metadata is derived from the return value.

# `span_session_turn`

```elixir
@spec span_session_turn(
  map(),
  ClaudeWrapper.Query.t(),
  (-&gt; {:ok, map(), ClaudeWrapper.Result.t()} | {:error, term()})
) :: {:ok, map(), ClaudeWrapper.Result.t()} | {:error, term()}
```

Wrap a session turn in a `[:claude_wrapper, :session, :turn, _]` span.

`fun` must return `{:ok, updated_session, %Result{}} | {:error, term()}`.

# `span_stream`

```elixir
@spec span_stream(ClaudeWrapper.Query.t(), (-&gt; Enumerable.t())) :: Enumerable.t()
```

Wrap a streaming query in a `[:claude_wrapper, :stream, _]` span.

`stream_fun` must return an `Enumerable.t()` of `%StreamEvent{}`.
The span's `:start` fires on first reduce, `:stop` fires when the
consumer halts or the producer is exhausted. `:stop` metadata
captures `cost_usd` / `session_id` from the terminal `result`
stream event, when observed.

---

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