Skip to main content

Observability

The AIM server emits OpenTelemetry traces, metrics, and logs over OTLP. Every authorization decision is traced, so you can see which agent took which action, which Fine-Grained Authorization step allowed or denied it, and how long each step took.

Demo stack

A hermetic demo stack lives in the repository: an OpenTelemetry Collector, Tempo, Prometheus, Loki, and Grafana. Bring it up and run the smoke test to confirm traces, metrics, and logs land end to end.

cd apps/backend/deployments/otel-demo
docker compose up -d
./smoke-backend.sh

Authorization spans

Every fga.authorize decision lands as a parent span with five child spans, one per Fine-Grained Authorization step. The parent span carries a fixed set of semantic-convention attributes so traces are queryable by agent, capability, and outcome.

AttributeMeaning
agent.idThe AIM agent UUID
agent.public_key.algorithmSigning algorithm for the agent identity
agent.trust_scoreTrust score at decision time
agent.drift_scoreBehavioral drift signal
agent.scan_verdictLatest scan verdict for the agent
agent.capabilityThe capability being authorized
fga.stepThe Fine-Grained Authorization step
fga.outcomeAllowed or denied
fga.denied_byThe step that denied, when denied

These attribute names are proposed to the OpenTelemetry Semantic Conventions working group, so AIM traces use stable, vendor-neutral names rather than custom keys.

Metrics and logs

Authorization decisions are also emitted as counters and as structured logs that share the same attribute keys, so a Prometheus query, a Tempo trace, and a Loki log line for the same decision line up on agent.id and fga.outcome.

Full design notes, the complete attribute reference, and exporter configuration are in apps/backend/docs/OBSERVABILITY.md in the repository.

Query examples

Because the signals share attribute keys, the same decision is reachable from traces, metrics, and logs. A few starting queries:

Tempo (TraceQL): denied authorizations for one agent

{name="fga.authorize" && .agent.id="<uuid>" && .fga.outcome != "ALLOW"}

Prometheus (PromQL): decision rate by outcome, and p99 latency

sum by (fga_outcome) (rate(fga_decisions_total[1m]))

histogram_quantile(0.99, sum by (le) (rate(fga_latency_ms_bucket[5m])))

Loki (LogQL): denial log lines, click through to the trace

{service_name="aim-backend"} |= "fga.decision" | json | fga_outcome != "ALLOW"

Related documentation