Skip to content
Learn/Structured Logging

Structured Logging

Making your systems observable and debuggable

Key Takeaways

  • Structured JSON logs are machine-parseable — text logs are not. This matters at scale when you need to search and aggregate.
  • Every log entry needs a request ID (correlation ID) to trace a single request across all log lines it generates
  • Log level should reflect the outcome: info for success, error for failures — this enables filtering and alerting
  • Measure and log response duration for every request — it is your first observability metric

What is Structured Logging?

Structured logging means emitting log entries as machine-parseable data (typically JSON) instead of free-form text. Each log line is a JSON object with consistent fields — timestamp, level, message, request ID, duration, and any other context.

Instead of: console.log("User logged in: john")

You emit: {"timestamp":"2024-01-15T10:30:00Z","level":"info","event":"user_login","username":"john","durationMs":45}

Why It Matters

At scale, you'll have thousands of log lines per second across dozens of services. When something goes wrong, you need to:

  • Search: "Show me all errors from the payments service in the last hour"
  • Correlate: "Show me every log line related to this specific user request"
  • Aggregate: "What's the p99 response time for the /orders endpoint?"
  • Alert: "Notify me when error rate exceeds 1%"

None of this is possible with unstructured text logs. Log aggregation tools (Elasticsearch, Datadog, CloudWatch) can only index and query structured fields.

How It Works

The Middleware Pattern

In Express/Fastify, logging is typically implemented as middleware that wraps every request:

  • On request start: Record the start time, extract or generate a request ID
  • On response finish: Calculate duration, capture status code, emit the log entry
  • Attach request ID: Set it on the response header so clients can reference it in support tickets

Correlation IDs

A correlation ID (or request ID) is a unique identifier that follows a request through your entire system. When a client sends X-Request-ID: abc-123, every service that handles that request includes abc-123 in its logs. This lets you search for a single request ID and see its entire journey.

If the client doesn't send one, generate a UUID and use that.

Log Levels

  • info: Normal operations — request completed successfully
  • warn: Recoverable issues — deprecated endpoint called, slow query
  • error: Failures — unhandled exception, dependency timeout, 5xx response

Set the level based on the HTTP status code: statusCode >= 500 → error, statusCode >= 400 → warn or error, everything else → info.

Common Mistakes

  • Using console.log(object): This produces [Object object] or pretty-printed output, not valid JSON. Always use JSON.stringify().
  • Missing request IDs: Without correlation IDs, debugging distributed systems is like finding a needle in a haystack.
  • Logging too much: Logging request/response bodies at info level can leak PII and overwhelm your log storage. Log bodies at debug level only.

Practice This Concept

Apply what you've learned by solving this challenge.

SystemTrials — Backend Engineering Practice That Tests Like Production