if Code.ensure_loaded?(:telemetry) do defmodule Puck.Telemetry do @moduledoc """ Telemetry integration for observability. Puck automatically emits telemetry events when the `:telemetry` dependency is installed. No configuration is required. ## Events ### Call Start `[:puck, :call, :start]` - Executed before the LLM call. #### Measurements * `:system_time` - The system time in native units. #### Metadata * `:client` - The `Puck.Client` struct. * `:prompt` - The prompt content. * `:context` - The `Puck.Context` struct. ### Call Stop `[:puck, :call, :stop]` - Executed after a successful LLM call. #### Measurements * `:duration` - Time taken in native units. #### Metadata * `:client` - The `Puck.Client` struct. * `:response` - The `Puck.Response` struct. * `:context` - The `Puck.Context` struct. ### Call Exception `[:puck, :call, :exception]` - Executed when the call fails. #### Measurements * `:duration` - Time taken before failure in native units. #### Metadata * `:client` - The `Puck.Client` struct. * `:context` - The `Puck.Context` struct. * `:kind` - The exception type (`:error`, `:exit`, or `:throw`). * `:reason` - The error reason. * `:stacktrace` - The stacktrace (may be empty). ### Stream Start `[:puck, :stream, :start]` - Executed before streaming begins. #### Measurements * `:system_time` - The system time in native units. #### Metadata * `:client` - The `Puck.Client` struct. * `:prompt` - The prompt content. * `:context` - The `Puck.Context` struct. ### Stream Chunk `[:puck, :stream, :chunk]` - Executed for each streamed chunk. #### Measurements No measurements. #### Metadata * `:client` - The `Puck.Client` struct. * `:chunk` - The chunk data. * `:context` - The `Puck.Context` struct. ### Stream Stop `[:puck, :stream, :stop]` - Executed after streaming completes. #### Measurements * `:duration` - Time taken in native units. #### Metadata * `:client` - The `Puck.Client` struct. * `:context` - The `Puck.Context` struct. ### Stream Exception `[:puck, :stream, :exception]` - Executed when streaming initialization fails. #### Measurements * `:duration` - Time taken before failure in native units. #### Metadata * `:client` - The `Puck.Client` struct. * `:context` - The `Puck.Context` struct. * `:kind` - The exception type (`:error`, `:exit`, or `:throw`). * `:reason` - The error reason. * `:stacktrace` - The stacktrace (may be empty). ### Backend Request `[:puck, :backend, :request]` - Executed before the backend request. #### Measurements * `:system_time` - The system time in native units. #### Metadata * `:config` - The backend configuration. * `:messages` - The messages being sent. ### Backend Response `[:puck, :backend, :response]` - Executed after the backend response. #### Measurements * `:system_time` - The system time in native units. #### Metadata * `:config` - The backend configuration. * `:response` - The backend response. ### Backend BAML Error `[:puck, :backend, :baml, :error]` — Emitted when the BAML backend returns an error. Includes the raw LLM response from the collector so callers can inspect what the model actually returned. #### Measurements No measurements. #### Metadata * `:function` - The BAML function name. * `:reason` - The error reason (string from the NIF). * `:raw_llm_response` - The raw LLM response string, or `nil` if unavailable. > The `:raw_llm_response` value is the unredacted model output. If the > prompt contained sensitive data, the response may echo it back. Redact > before logging in production. ### Compaction Start `[:puck, :compaction, :start]` - Executed before context compaction. #### Measurements * `:system_time` - The system time in native units. #### Metadata * `:context` - The `Puck.Context` struct before compaction. * `:strategy` - The compaction strategy module. * `:config` - The compaction configuration. ### Compaction Stop `[:puck, :compaction, :stop]` - Executed after successful compaction. #### Measurements * `:duration` - Time taken in native units. * `:messages_before` - Message count before compaction. * `:messages_after` - Message count after compaction. #### Metadata * `:context` - The `Puck.Context` struct after compaction. * `:strategy` - The compaction strategy module. ### Compaction Error `[:puck, :compaction, :error]` - Executed when compaction fails. #### Measurements * `:duration` - Time taken before failure in native units. #### Metadata * `:context` - The `Puck.Context` struct. * `:strategy` - The compaction strategy module. * `:reason` - The error reason. ### LiveView Stream Start `[:puck, :live_view, :stream, :start]` — Emitted when a LiveView stream begins. #### Measurements * `:system_time` - The system time in native units. #### Metadata * `:stream_id` - The stream identifier. * `:client` - The `Puck.Client` struct. ### LiveView Stream Stop `[:puck, :live_view, :stream, :stop]` — Emitted when a LiveView stream completes. #### Measurements * `:duration` - Time taken in native units. #### Metadata * `:stream_id` - The stream identifier. * `:response` - The `Puck.Response` struct. ### LiveView Stream Error `[:puck, :live_view, :stream, :error]` — Emitted when a LiveView stream fails. #### Measurements No measurements. #### Metadata * `:stream_id` - The stream identifier. * `:reason` - The error reason. ### LiveView Stream Cancel `[:puck, :live_view, :stream, :cancel]` — Emitted when a LiveView stream is cancelled. #### Measurements No measurements. #### Metadata * `:stream_id` - The stream identifier. * `:content` - The accumulated content at cancellation. ### LiveView Stream Handler Error `[:puck, :live_view, :stream, :handler_error]` — Emitted when a `Puck.LiveView.Handler` callback raises. #### Measurements No measurements. #### Metadata * `:stream_id` - The stream identifier. * `:handler` - The handler module. * `:callback` - The callback that raised (e.g. `:on_chunk`, `:on_done`). * `:reason` - The exception. ## Attaching Handlers :telemetry.attach_many("my-handler", Puck.Telemetry.event_names(), &handler/4, nil) # Or use the default logger Puck.Telemetry.attach_default_logger() """ @doc """ Returns all telemetry event names that can be emitted. Useful for attaching handlers to all events. ## Example :telemetry.attach_many("my-handler", Puck.Telemetry.event_names(), &handler/4, nil) """ def event_names do [ [:puck, :call, :start], [:puck, :call, :stop], [:puck, :call, :exception], [:puck, :stream, :start], [:puck, :stream, :chunk], [:puck, :stream, :stop], [:puck, :stream, :exception], [:puck, :backend, :request], [:puck, :backend, :response], [:puck, :backend, :baml, :error], [:puck, :compaction, :start], [:puck, :compaction, :stop], [:puck, :compaction, :error], [:puck, :live_view, :stream, :start], [:puck, :live_view, :stream, :stop], [:puck, :live_view, :stream, :error], [:puck, :live_view, :stream, :cancel], [:puck, :live_view, :stream, :handler_error] ] end @doc """ Attaches a default logging handler to all Puck telemetry events. This is a convenience function for quick debugging. For production use, you should implement your own handler with appropriate log levels and formatting. ## Options - `:level` - Log level to use (default: `:debug`) ## Example Puck.Telemetry.attach_default_logger() Puck.Telemetry.attach_default_logger(level: :info) """ def attach_default_logger(opts \\ []) do level = Keyword.get(opts, :level, :debug) :telemetry.attach_many( "puck-telemetry-default-logger", event_names(), &__MODULE__.log_event/4, %{level: level} ) end @doc """ Detaches the default logging handler. """ def detach_default_logger do :telemetry.detach("puck-telemetry-default-logger") end @doc false def log_event(event, measurements, metadata, config) do require Logger level = Map.get(config, :level, :debug) event_name = Enum.join(event, ".") Logger.log(level, fn -> "[#{event_name}] #{inspect(measurements)} #{inspect(metadata, limit: 3)}" end) end end end