Skip to content

All lessons

Area 08 // BackendLesson 139 / 14013 min read

Logging and monitoring

Logging records what your backend did, ideally as structured JSON lines with a request ID that ties together everything one request touched, and monitoring turns logs, metrics and traces into dashboards and alerts so you notice slow, failing or expensive behaviour before users report it.

Think of it like this

Analogy

A ship's logbook: every watch writes short, standard entries (time, position, speed, events), so anyone can reconstruct the voyage later. The instruments on the bridge (monitoring) show the current speed and depth at a glance, and an alarm sounds when depth gets dangerous. A logbook written as rambling diary entries, or one that records the safe's combination, would be useless or dangerous.

The problem it solves

A user says "the bot gave me nonsense at 10:15". Without good logs you can't find their request, see which model and prompt ran, how many tokens it used, which documents were retrieved, or why it was slow. With too many unstructured logs you drown; with logs full of personal data and keys you create a security problem. You need structured, correlated logs plus metrics that feed dashboards and alerts.

How it works, step by step

  1. Log structured events: one JSON object per line with level, msg, timestamp, service, request_id and relevant fields (see the code). Libraries: Python's logging with a JSON formatter, structlog, or your platform's agent.
  2. Use levels deliberately: INFO for normal milestones (request received, LLM call done), WARNING for handled oddities (retry, fallback used), ERROR for failures, DEBUG only in development.
  3. Correlate: create a request ID (or use the trace ID) at the edge, store it in a context variable, include it in every log line, pass it to downstream calls and return it in error responses so users can quote it.
  4. Log what AI debugging needs: model and prompt version, input and output token counts, latency and TTFT, cost, cache hits, retrieved document IDs, tool calls and their results, guardrail decisions (tracing captures the full detail).
  5. Protect data: never log keys or tokens; redact or hash personal data; avoid full prompts in general logs (PII redaction); restrict access and set retention.
  6. Ship logs centrally: a log platform (cloud logging, Grafana Loki, Elastic, Datadog and others) for search and retention.
  7. Metrics for dashboards and alerts: request rate, error rate, latency percentiles, tokens and cost, queue depth (alerts and dashboards); OpenTelemetry can produce logs, metrics and traces with one SDK.
  8. Audit logs are separate: security-relevant actions go to a tamper-resistant store (audit logging).

See it in code

Needs
Nothing: plain Python 3 (logging and contextvars are built in).

JSON logs with a request ID carried through each request, and the fields worth recording for an LLM call.

json_logging.py
import contextvars, json, logging, sys
 
request_id = contextvars.ContextVar("request_id", default="-")
 
class JsonFormatter(logging.Formatter):
    """One JSON object per line: easy for log tools to search, filter and chart."""
    def format(self, record):
        entry = {"level": record.levelname, "msg": record.getMessage(), "request_id": request_id.get()}
        entry.update(getattr(record, "fields", {}))
        return json.dumps(entry)
 
log = logging.getLogger("rag-app")
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
log.addHandler(handler)
log.setLevel(logging.INFO)
 
def handle(question: str, user_id: str, fake_latency_ms: int, fake_error: bool = False):
    request_id.set(f"req-{len(question):04d}")       # in real code: str(uuid.uuid4()) or the trace ID
    log.info("request received", extra={"fields": {"user": user_id, "question_chars": len(question)}})
    if fake_error:
        log.error("llm call failed", extra={"fields": {"provider": "anthropic", "status": 529, "attempt": 3}})
        return
    log.info("llm call done", extra={"fields": {"model": "claude-haiku-5-5", "input_tokens": 1830,
             "output_tokens": 212, "latency_ms": fake_latency_ms, "cost_usd": 0.000289}})
 
handle("How many days of leave?", "u-123", 840)
handle("Expense limit?", "u-456", 0, fake_error=True)
# Never log raw secrets or full personal data; log IDs, sizes, counts and redacted text instead.
Output
{"level": "INFO", "msg": "request received", "request_id": "req-0023", "user": "u-123", "question_chars": 23}
{"level": "INFO", "msg": "llm call done", "request_id": "req-0023", "model": "claude-haiku-5-5", "input_tokens": 1830, "output_tokens": 212, "latency_ms": 840, "cost_usd": 0.000289}
{"level": "INFO", "msg": "request received", "request_id": "req-0014", "user": "u-456", "question_chars": 14}
{"level": "ERROR", "msg": "llm call failed", "request_id": "req-0014", "provider": "anthropic", "status": 529, "attempt": 3}

Filtering by request_id = "req-0014" shows that user's whole story; filtering by level = "ERROR" and grouping by status shows how often the provider was overloaded. The question length is logged, not the question itself. The cost matches Claude Haiku 5.5's price as of November 2026.

A real example

Example

Finding one bad answer in a million requests

A user reported a wrong answer and quoted the request ID shown in the app's error panel. The team searched their log platform for it and saw the request, retrieval of three chunks (IDs logged), prompt version support@v14, model, tokens and a guardrail warning about low retrieval scores. The linked trace showed the full prompt. The retrieved chunks came from an outdated document; they fixed the index and added the question to the golden set, all within an hour, without anyone grepping text logs on servers.

The easy way

Start here

Use a JSON log formatter, add a request ID middleware, log one line when a request arrives and one per LLM call with model, tokens, latency and cost, and send logs to your platform's log viewer. Don't log prompts with personal data.

When you grow: the production setup

Teams use OpenTelemetry for logs, metrics and traces with shared IDs, structured logging libraries, sampling for high-volume debug logs, redaction processors, log-based metrics and alerts, per-tenant cost dashboards, access controls and retention policies, and runbooks that start from a request ID.

Trade-offs

What you gain

  • Fast debugging of individual requests.
  • Logs feed metrics, dashboards and alerts.
  • Shared request IDs connect services and traces.

What it costs you

  • Log volume costs money to ship and store.
  • Logs can leak sensitive data if careless.
  • Too many logs hide the important ones.

When not to use it

  • Don't log full prompts and responses in general application logs; use access-controlled tracing with redaction for that.

Common mistakes

  • Free-text logs that can't be filtered.
  • No request ID, so lines from concurrent requests can't be separated.
  • Logging API keys, tokens or personal data.
  • DEBUG level in production, burying everything.
  • No token or cost fields, so spend can't be explained.

Interview questions

Try answering out loud first, then open the model answer.

What would you log for each LLM request?

Model answer

Request ID or trace ID, user or tenant ID (not personal details), route, model and prompt version, input and output tokens, latency and TTFT, cost, status and error codes, retries and fallbacks, cache hits, retrieved document IDs, tool calls and guardrail decisions, with secrets excluded and personal data redacted.

Logs, metrics and traces: what's the difference?

Model answer

Logs are discrete event records with context, good for investigating specific requests. Metrics are numeric time series (counts, rates, percentiles), cheap to store and ideal for dashboards and alerts. Traces show the path and timing of one request across components (spans), ideal for latency and flow analysis. Shared IDs link them.

How do you keep logs useful but safe?

Model answer

Structured fields, consistent names and levels; request IDs; log identifiers and sizes rather than raw content; redact personal data and never log secrets; restrict access; set retention; sample noisy logs; and alert on log-derived metrics rather than reading logs manually.

Quiz

Pick an answer to see whether it’s right and why.

  1. 01Why log in JSON instead of free text?
  2. 02What does a request ID do?
  3. 03Which belongs in a log line for an LLM call?
  4. 04Logs vs metrics?
  5. 05Why set a retention period for logs?

0 of 5 answered

Key terms

Structured logging
Writing logs as machine-readable records (usually JSON) with named fields, so they can be searched, filtered and charted.
Log level
The severity of a log message, such as DEBUG, INFO, WARNING, ERROR or CRITICAL, used to filter what gets recorded and shown.
Request ID
A unique ID assigned to each incoming request and included in every related log line, call and trace.
Metrics
Numbers measured over time, such as request rate, error rate, latency percentiles or cost per hour, used for dashboards and alerts.
Observability
Being able to understand what a system is doing from its outputs (logs, metrics and traces), so you can find out why a request was slow, wrong or expensive.

Before and after

Progress is saved in this browser only.