ClaudeWrapper.Telemetry (ClaudeWrapper v0.14.0)

Copy Markdown View Source

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

Summary

Functions

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

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

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

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.

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

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

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

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

Types

metadata()

@type metadata() :: %{optional(atom()) => term()}

Functions

duplex_session_start(metadata)

@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(monotonic, metadata)

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

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

duplex_turn_exception(arg1, reason)

@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(metadata)

@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(arg1, extra)

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

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

span_exec(query, fun)

@spec span_exec(ClaudeWrapper.Query.t(), (-> {: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(session, query, fun)

@spec span_session_turn(
  map(),
  ClaudeWrapper.Query.t(),
  (-> {: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(query, stream_fun)

@spec span_stream(ClaudeWrapper.Query.t(), (-> 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.