AgentDb.Observability (agent_db v0.1.0)

Copy Markdown View Source

Stateless runtime diagnostics: low-cardinality telemetry, distributed traces, and redacted structured logs.

No process, no endpoint, no exporter. :telemetry events are always emitted locally; OpenTelemetry spans are no-ops unless the host configures an SDK. Failures here never replace an operation result.

Summary

Functions

Emits a job queue/execution measurement with queue-wait vs execution split.

Emits a model load/inference measurement.

Machine-readable code for a store error: the classified tag as a string, shared by every transport error response and by telemetry/logging.

JSON-safe, detail-free message for a store error, for transport responses.

Extracts trace context from HTTP headers or WS event metadata (never rejects).

Extracts W3C trace context from a Plug connection (never rejects).

HTTP status for a store error: 404 for missing, 503 for retryable unavailability, 422/413/429 for caller errors, 401 for authorization, and 500 for server-side failures and anything unrecognised.

Structured operational log without content, prompts, or credentials.

Counts of operations, jobs, and model calls by outcome, as %{{family, dimensions, outcome} => count}.

Parses a W3C traceparent value. Returns {:ok, %{trace_id, span_id}} or :error for missing/malformed input (caller starts a new trace).

Recent failures, newest first, each with the operation it happened in and its classified reason.

Redacts credentials and secret query params from a URL for logging.

Runs a retrieval stage with a bounded OTel span and telemetry measurement.

Runs fun, emitting a low-cardinality operation measurement.

Runs fun with correlation added to this process's log correlation.

Runs fun inside an OTel span when available, otherwise runs directly.

Functions

emit_job(kind, outcome, queue_wait_ms, execution_ms, reason \\ nil)

@spec emit_job(
  atom() | term(),
  :ok | :error | atom(),
  non_neg_integer(),
  non_neg_integer(),
  term()
) ::
  :ok

Emits a job queue/execution measurement with queue-wait vs execution split.

emit_model(role, outcome, duration_ms, reason \\ nil)

@spec emit_model(atom() | term(), atom(), non_neg_integer(), term()) :: :ok

Emits a model load/inference measurement.

error_code(reason)

@spec error_code(term()) :: String.t()

Machine-readable code for a store error: the classified tag as a string, shared by every transport error response and by telemetry/logging.

The code set is the classify_reason/1 taxonomy: caller errors (invalid_mode, invalid_uri, invalid_query, invalid_limit, not_a_memory_uri, missing_argument, unknown_event, unknown_tool, unsafe_path, …), missing (not_found, no_memory), retryable (model_loading, background_jobs_pending), and server failures (inference_failed, inference_timeout, model_load_failed, download_failed, background_jobs_failed, hybrid_leg_timeout, hybrid_leg_failed, …), with anything unrecognised as "error". A new failure mode reuses an existing code or extends the taxonomy; transports never emit free-form text as the only signal.

error_message(reason)

@spec error_message(term()) :: String.t()

JSON-safe, detail-free message for a store error, for transport responses.

Renders the classified tag only, never the detail: details routinely carry URIs, queries, and paths, which must not leak into client-facing payloads. Always encodable (a plain string), so no error term can break an encoder.

extract_context(headers)

@spec extract_context(map() | keyword() | list()) :: map() | nil

Extracts trace context from HTTP headers or WS event metadata (never rejects).

from_conn(conn)

@spec from_conn(Plug.Conn.t()) :: map() | nil

Extracts W3C trace context from a Plug connection (never rejects).

http_status(reason)

@spec http_status(term()) :: 400 | 401 | 404 | 413 | 422 | 429 | 500 | 503

HTTP status for a store error: 404 for missing, 503 for retryable unavailability, 422/413/429 for caller errors, 401 for authorization, and 500 for server-side failures and anything unrecognised.

log(level, fields)

@spec log(atom(), keyword()) :: :ok

Structured operational log without content, prompts, or credentials.

operation_stats()

@spec operation_stats() :: map()

Counts of operations, jobs, and model calls by outcome, as %{{family, dimensions, outcome} => count}.

Dimensions are only the bounded labels the events already carry -- operation, kind, role -- so the number of counters follows the store's code rather than the traffic through it.

parse_traceparent(value)

@spec parse_traceparent(String.t() | nil) :: {:ok, map()} | :error

Parses a W3C traceparent value. Returns {:ok, %{trace_id, span_id}} or :error for missing/malformed input (caller starts a new trace).

recent_errors(limit \\ 20)

@spec recent_errors(pos_integer()) :: [map()]

Recent failures, newest first, each with the operation it happened in and its classified reason.

Read from Observability.Sink, which records the failures among the events above. Entries carry a classification and a timestamp only -- never a URI, document content, a prompt, a user, or a credential -- and the list is bounded and in memory, so it starts empty again on a restart rather than becoming a history the store accumulates forever.

A store whose sink is not running answers an empty list rather than an error: diagnostics are never a reason an operation fails.

redact_url(url)

@spec redact_url(String.t()) :: String.t()

Redacts credentials and secret query params from a URL for logging.

stage(stage_name, mode, fun)

@spec stage(atom(), atom() | nil, (-> result)) :: result when result: var

Runs a retrieval stage with a bounded OTel span and telemetry measurement.

Only stage, mode, and outcome are recorded -- never URIs, content, prompts, users, or credentials. A missing or malformed parent context starts a new trace without changing the result.

timed(operation, meta \\ %{}, fun)

@spec timed(atom(), map(), (-> result)) :: result when result: var

Runs fun, emitting a low-cardinality operation measurement.

Metadata carries only operation and outcome (plus kind/role where relevant) — never URIs, content, prompts, users, credentials, or tokens.

with_correlation(correlation, fun)

@spec with_correlation(map(), (-> result)) :: result when result: var

Runs fun with correlation added to this process's log correlation.

Correlation is process-scoped and additive: a nested context adds to what is already there, and the previous value is restored when fun returns, raises, or exits. Long-lived processes — workers, channels — rely on that restore so a correlation never attaches to an unrelated later operation. Only the bounded correlation fields are kept, and a nil entry never clears one already set.

with_span(name, attrs \\ %{}, fun)

@spec with_span(String.t(), map(), (-> result)) :: result when result: var

Runs fun inside an OTel span when available, otherwise runs directly.