Jido.Signal.TraceContext (Jido Signal v2.2.2)

View Source

Process-dictionary-based trace context management.

Stores current trace context in the process dictionary so it can be accessed when creating new signals without explicit parameter passing.

Usage Pattern

  1. Ingress: When receiving a signal, set context from the signal
  2. Processing: Access context when creating outbound signals
  3. Egress: Clear context when done

Examples

# At ingress point (e.g., AgentServer.handle_call)
{signal, ctx} = TraceContext.ensure_from_signal(signal)

# During processing - context available automatically
ctx = TraceContext.current()

# When emitting signals, propagate trace as child
{:ok, traced_signal} = TraceContext.propagate_to(outbound_signal, input_signal.id)

# At egress
TraceContext.clear()

Thread Safety

Process dictionary is per-process, so context is automatically isolated. For Task.async or spawn, context must be explicitly passed and restored.

Example: Spawning with Context

# Capture context before spawning
ctx = TraceContext.current()

Task.async(fn ->
  # Restore context in spawned process
  TraceContext.set(ctx)

  # Now context is available in the spawned task
  TraceContext.current()
end)

See Also

Summary

Functions

Builds child context from current process context.

Clears the trace context from the process dictionary.

Gets the current trace context from the process dictionary.

Ensures trace context is set from a signal.

Ensures trace context is set from a state map containing a current_signal.

Adds current trace context as child to an outbound signal.

Sets the trace context in the process dictionary.

Sets trace context from a signal's extension data.

Converts current trace context to metadata map for telemetry/observability.

Converts a trace context to metadata map for telemetry/observability.

Wraps a function with trace context preservation.

Functions

child_context(causation_id \\ nil)

@spec child_context(String.t() | nil) :: Jido.Signal.Trace.Context.t()

Builds child context from current process context.

If there's a current trace context, creates a child span linked to it. If no current context, creates a new root trace.

Examples

# With existing context - creates child
TraceContext.set(parent_ctx)
child = TraceContext.child_context("input-signal-id")
child.trace_id #=> parent_ctx.trace_id (inherited)
child.parent_span_id #=> parent_ctx.span_id (linked)

# Without context - creates root
TraceContext.clear()
root = TraceContext.child_context("input-signal-id")
root.parent_span_id #=> nil

clear()

@spec clear() :: :ok

Clears the trace context from the process dictionary.

Returns :ok.

Examples

iex> TraceContext.set(ctx)
iex> TraceContext.clear()
:ok
iex> TraceContext.current()
nil

current()

@spec current() :: Jido.Signal.Trace.Context.t() | nil

Gets the current trace context from the process dictionary.

Returns nil if no context is set.

Examples

iex> TraceContext.current()
nil

iex> TraceContext.set(ctx)
iex> TraceContext.current()
%Context{trace_id: "abc123", span_id: "def456"}

ensure_from_signal(signal, opts \\ [])

@spec ensure_from_signal(
  Jido.Signal.t(),
  keyword()
) :: {Jido.Signal.t(), Jido.Signal.Trace.Context.t()}

Ensures trace context is set from a signal.

If the signal has trace context, extracts and sets it. If not, creates a new root trace, adds it to the signal, and sets it.

Returns {signal, trace_context} where signal may be updated with trace.

Options

  • :causation_id - Optional causation reference for new root traces
  • :tracestate - Optional W3C tracestate for new root traces

Examples

# Signal without trace - creates new root
{traced_signal, ctx} = TraceContext.ensure_from_signal(signal)

# Signal with trace - uses existing
{signal, ctx} = TraceContext.ensure_from_signal(traced_signal)

ensure_set_from_state(arg1)

@spec ensure_set_from_state(map()) :: :ok | :error

Ensures trace context is set from a state map containing a current_signal.

This is a convenience for extracting trace from agent state patterns that store the current signal being processed.

Returns :ok if context was set, :error if no signal or no trace.

Examples

state = %{current_signal: traced_signal, other: :data}
:ok = TraceContext.ensure_set_from_state(state)

propagate_to(signal, causation_id \\ nil)

@spec propagate_to(Jido.Signal.t(), String.t() | nil) ::
  {:ok, Jido.Signal.t()} | {:error, term()}

Adds current trace context as child to an outbound signal.

Creates a child span linked to the current process context and adds it to the signal.

Examples

# Set up parent context
TraceContext.set(parent_ctx)

# Create outbound signal
{:ok, signal} = Signal.new("user.created", %{user_id: "123"})

# Propagate trace as child
{:ok, traced} = TraceContext.propagate_to(signal, "input-signal-id")
Trace.get(traced).trace_id #=> parent_ctx.trace_id (inherited)
Trace.get(traced).parent_span_id #=> parent_ctx.span_id (linked)
Trace.get(traced).causation_id #=> "input-signal-id"

set(context)

@spec set(Jido.Signal.Trace.Context.t()) :: :ok

Sets the trace context in the process dictionary.

Returns :ok.

Examples

iex> TraceContext.set(ctx)
:ok

set_from_signal(signal)

@spec set_from_signal(Jido.Signal.t()) :: :ok | :error

Sets trace context from a signal's extension data.

Extracts trace context from the signal's correlation extension and sets it in the process dictionary.

Returns :ok if context was set, :error if signal has no trace.

Examples

:ok = TraceContext.set_from_signal(traced_signal)

to_telemetry_metadata()

@spec to_telemetry_metadata() :: map()

Converts current trace context to metadata map for telemetry/observability.

Returns a flat map with jido_ prefixed keys suitable for telemetry metadata.

Examples

TraceContext.set(ctx)
TraceContext.to_telemetry_metadata()
#=> %{
#=>   jido_trace_id: "abc",
#=>   jido_span_id: "def",
#=>   jido_parent_span_id: "parent",
#=>   jido_causation_id: nil
#=> }

to_telemetry_metadata(ctx)

@spec to_telemetry_metadata(Jido.Signal.Trace.Context.t() | nil) :: map()

Converts a trace context to metadata map for telemetry/observability.

Examples

TraceContext.to_telemetry_metadata(ctx)
#=> %{jido_trace_id: "abc", jido_span_id: "def", ...}

TraceContext.to_telemetry_metadata(nil)
#=> %{jido_trace_id: nil, jido_span_id: nil, ...}

with_context(context_or_signal, fun)

@spec with_context(Jido.Signal.t() | Jido.Signal.Trace.Context.t(), (-> result)) ::
  result
when result: term()

Wraps a function with trace context preservation.

Sets context from the signal or context before executing the function, and clears it after (even if the function raises).

Examples

# With a signal
result = TraceContext.with_context(traced_signal, fn ->
  # Context is available here
  ctx = TraceContext.current()
  # ... do work ...
  :result
end)
# Context is cleared after

# With explicit context
result = TraceContext.with_context(ctx, fn ->
  TraceContext.current() #=> ctx
  :result
end)