Jido.Observe (Jido v2.0.0-rc.1)

View Source

Unified observability façade for Jido agents.

Wraps :telemetry events and Logger with a simple API for observing agent execution, action invocations, and workflow iterations.

Features

  • Automatic telemetry event emission (start/stop/exception)
  • Duration measurement for all spans (nanoseconds)
  • Automatic correlation ID enrichment from Jido.Tracing.Context
  • Extension point for future OpenTelemetry integration via Jido.Observe.Tracer
  • Threshold-based logging via Jido.Observe.Log

Correlation Tracing Integration

When Jido.Tracing.Context has an active trace context (set via signal processing), all spans automatically include correlation metadata:

  • :jido_trace_id - shared trace identifier across the call chain
  • :jido_span_id - unique span identifier for the current signal
  • :jido_parent_span_id - parent span that triggered this signal
  • :jido_causation_id - signal ID that caused this signal

This connects timed telemetry spans with signal causation tracking automatically.

Configuration

config :jido, :observability,
  log_level: :info,
  tracer: Jido.Observe.NoopTracer

Usage

Synchronous work

Jido.Observe.with_span([:jido, :agent, :action, :run], %{agent_id: id, action: "my_action"}, fn ->
  # Your code here
  {:ok, result}
end)

Asynchronous work (Tasks)

span_ctx = Jido.Observe.start_span([:jido, :agent, :async, :request], %{agent_id: id})

Task.start(fn ->
  try do
    result = do_async_work()
    Jido.Observe.finish_span(span_ctx, %{result_size: byte_size(result)})
    result
  rescue
    e ->
      Jido.Observe.finish_span_error(span_ctx, :error, e, __STACKTRACE__)
      reraise e, __STACKTRACE__
  end
end)

Telemetry Events

All spans emit standard telemetry events:

  • event_prefix ++ [:start] - emitted when span starts
  • event_prefix ++ [:stop] - emitted on successful completion
  • event_prefix ++ [:exception] - emitted on error

Measurements include:

  • :system_time - start timestamp (nanoseconds)
  • :duration - elapsed time (nanoseconds, on stop/exception)
  • Any additional measurements passed to finish_span/2

Metadata Best Practices

Metadata should be small, identifying data (IDs, step numbers, model names), not full prompts/responses. For large payloads, include derived measurements (prompt_tokens, prompt_size_bytes) rather than the raw content.

Summary

Functions

Checks if debug events are enabled in configuration.

Emits a debug event only if debug events are enabled in config.

Finishes a span successfully.

Conditionally logs a message based on the observability threshold.

Redacts sensitive data based on configuration.

Starts an async span for work that will complete later.

Wraps synchronous work with telemetry span events.

Types

event_prefix()

@type event_prefix() :: [atom()]

measurements()

@type measurements() :: map()

metadata()

@type metadata() :: map()

span_ctx()

@type span_ctx() :: Jido.Observe.SpanCtx.t()

Functions

debug_enabled?()

@spec debug_enabled?() :: boolean()

Checks if debug events are enabled in configuration.

Returns

true if :debug_events is :all or :minimal, false otherwise.

emit_debug_event(event_prefix, measurements \\ %{}, metadata \\ %{})

@spec emit_debug_event(event_prefix(), measurements(), metadata()) :: :ok

Emits a debug event only if debug events are enabled in config.

This helper checks the :debug_events config before emitting, ensuring zero overhead when debugging is disabled (production default).

Configuration

# config/dev.exs
config :jido, :observability,
  debug_events: :all  # or :minimal, :off

# config/prod.exs
config :jido, :observability,
  debug_events: :off

Parameters

  • event_prefix - Telemetry event name
  • measurements - Map of measurements (durations, counts, etc.)
  • metadata - Map of metadata (agent_id, iteration, etc.)

Example

Jido.Observe.emit_debug_event(
  [:jido, :agent, :iteration, :stop],
  %{duration: 1_234_567},
  %{agent_id: agent.id, iteration: 3, status: :awaiting_tool}
)

finish_span(span_ctx, extra_measurements \\ %{})

@spec finish_span(span_ctx(), measurements()) :: :ok

Finishes a span successfully.

Parameters

  • span_ctx - The span context returned by start_span/2
  • extra_measurements - Additional measurements to include (e.g., token counts)

Example

Jido.Observe.finish_span(span_ctx, %{prompt_tokens: 100, completion_tokens: 50})

finish_span_error(span_ctx, kind, reason, stacktrace)

@spec finish_span_error(span_ctx(), atom(), term(), list()) :: :ok

Finishes a span with an error.

Parameters

  • span_ctx - The span context returned by start_span/2
  • kind - The error kind (:error, :exit, :throw)
  • reason - The error reason/exception
  • stacktrace - The stacktrace

Example

rescue
  e ->
    Jido.Observe.finish_span_error(span_ctx, :error, e, __STACKTRACE__)
    reraise e, __STACKTRACE__

log(level, message, metadata \\ [])

@spec log(Logger.level(), Logger.message(), keyword()) :: :ok

Conditionally logs a message based on the observability threshold.

Delegates to Jido.Observe.Log.log/3.

Example

Jido.Observe.log(:debug, "Processing step", agent_id: agent.id)

redact(value, opts \\ [])

@spec redact(
  term(),
  keyword()
) :: term()

Redacts sensitive data based on configuration.

When :redact_sensitive is true (production default), replaces the value with "[REDACTED]". Otherwise returns the value unchanged.

Configuration

# config/prod.exs
config :jido, :observability,
  redact_sensitive: true

# config/dev.exs
config :jido, :observability,
  redact_sensitive: false

Parameters

  • value - The value to potentially redact
  • opts - Optional keyword list with :force_redact override

Examples

# In production (redact_sensitive: true)
redact("secret data")
# => "[REDACTED]"

# In development (redact_sensitive: false)
redact("secret data")
# => "secret data"

# Force redaction regardless of config
redact("secret data", force_redact: true)
# => "[REDACTED]"

start_span(event_prefix, metadata)

@spec start_span(event_prefix(), metadata()) :: span_ctx()

Starts an async span for work that will complete later.

Use this for Task-based operations where you can't use with_span/3. You must call finish_span/2 or finish_span_error/4 when the work completes.

Parameters

  • event_prefix - List of atoms for the telemetry event name
  • metadata - Map of metadata to include in all events

Returns

A span context struct to pass to finish_span/2 or finish_span_error/4.

Example

span_ctx = Jido.Observe.start_span([:jido, :ai, :llm, :request], %{model: "claude"})

Task.start(fn ->
  result = do_work()
  Jido.Observe.finish_span(span_ctx, %{output_bytes: byte_size(result)})
end)

with_span(event_prefix, metadata, fun)

@spec with_span(event_prefix(), metadata(), (-> result)) :: result when result: term()

Wraps synchronous work with telemetry span events.

Emits :start event before executing the function, then either :stop on success or :exception if an error is raised. Duration is automatically measured.

Parameters

  • event_prefix - List of atoms for the telemetry event name (e.g., [:jido, :ai, :react, :step])
  • metadata - Map of metadata to include in all events
  • fun - Zero-arity function to execute

Returns

The return value of fun.

Example

Jido.Observe.with_span([:jido, :ai, :tool, :invoke], %{tool: "search"}, fn ->
  perform_search(query)
end)