Gapless

Logging

How to write structured, correlated logs for an AI system, what to log at each step, what never to log, and how to keep log volume and cost under control.

Logs record discrete events with context. In this system, traces carry the detailed step-by-step view of each request, and logs record notable events: errors, decisions, state changes, and warnings. Keep logs structured, correlated with traces, and free of content.

Structured logs

Every log line is a JSON object with standard fields. Free-text logs cannot be filtered, redacted reliably, or aggregated.

{
  "ts": "2026-03-14T09:31:07.412Z",
  "level": "warn",
  "service": "orchestrator",
  "event": "retrieval.low_relevance",
  "trace_id": "7f3a9c2e5b1d",
  "span_id": "a1b2c3d4",
  "tenant_id": "ten_acme",
  "user_id_hash": "u_5e1f...",
  "conversation_id": "conv_8f2k",
  "message_id": "msg_91xa",
  "top_score": 0.21,
  "threshold": 0.35,
  "candidates": 50,
  "action": "answer_without_sources"
}
FieldRule
eventA stable name from a fixed vocabulary, such as model.fallback, guardrail.blocked, tool.rejected. Dashboards and alerts depend on it
trace_id, span_idAlways present, so any log line links to its trace
tenant_idAlways present for tenant-scoped work
User identityHashed or ID only. No emails or names in logs
ValuesNumbers and enums, not sentences

What to log

EventLevelKey fields
Request completedinfoRoute, status, duration, tokens, model, prompt version
Model fallback usedwarnPrimary model, fallback model, reason
Model call failedwarn or errorProvider, model, status, retryable
Stream stalled or droppedwarnTime since last token, tokens sent
Guardrail blockedinfoGuardrail, category, stage. No content
Retrieval returned nothing relevantinfoTop score, threshold
Tool call rejected by userinfoTool name
Tool call failedwarnTool, target system status
Agent hit step limitwarnSteps, last tool
Permission filter returned chunk without ACLerrorChunk ID. Should never happen
Context truncatedinfoPart truncated, tokens removed
Budget or rate limit reachedinfoLimit type, tenant
Job failed permanentlyerrorJob type, reason

What never to log

  • Message content, prompts, or model outputs. These go in traces with restricted access, if anywhere.
  • Document text or chunk content.
  • Secrets: API keys, tokens, passwords, connection strings. Including those that appear in request bodies or exception messages.
  • Personal data: names, emails, phone numbers. Use IDs.
  • Full provider error bodies, which can echo prompt content.

Enforce this in code, not by convention:

  • Use a logging wrapper that only accepts structured fields, and rejects or redacts fields named like prompt, content, text, body, message.
  • Scrub exceptions before logging: strip request bodies and provider responses.
  • Run a redaction processor in the telemetry collector as a second layer, with patterns for emails, card numbers, and key formats.
  • Periodically scan stored logs for leaked patterns, and treat findings as incidents.

Levels

LevelUse for
errorSomething failed that needs attention, or a condition that must never happen
warnDegraded but handled: fallback used, retry needed, limit reached
infoNotable normal events: request completed, guardrail decision, job finished
debugDetailed diagnostics, off in production by default, enabled per tenant or per request when debugging

Do not log at error for user mistakes such as invalid input. Error-level logs should be rare enough to read.

Volume and cost

AI systems generate a lot of telemetry: every request has many steps, and agents have many more. Logging costs can rival model costs if unmanaged.

  • Log one summary line per request, not one per step. Per-step detail lives in the trace.
  • Sample info logs for high-volume, low-value events, such as successful cache hits.
  • Never log per token.
  • Set retention by level: errors longer, info shorter.

Correlation in practice

To investigate a user report:

  1. The user provides the reference ID shown in the UI, which is the trace ID.
  2. Search logs by trace_id to see notable events: fallbacks, guardrails, low relevance.
  3. Open the trace to see every step's timing, the retrieved chunks, and the prompt version.
  4. Open the message record for sources used and feedback.

For a broad issue, filter logs by event and dimensions, for example event = model.fallback AND tenant_id = ten_acme over the last hour.

Checklist

  • All logs are structured JSON with stable event names.
  • Every log line has a trace ID, and tenant ID where relevant.
  • No content, secrets, or personal data in logs, enforced by a logging wrapper and collector redaction.
  • One summary log per request. Step detail lives in traces.
  • Error level is reserved for real failures and impossible conditions.
  • Log retention and sampling are set per level.

On this page