Start Learning
Javaneer
Back to stage
Module 8·Production & Modern Spring

Structured Logging

Logs machines can read: JSON logging, correlation with trace IDs, and log levels you can change at runtime.

12 min readIntermediate
On this page

When one app runs on one machine, System.out.println and a scroll of console text get you a long way. In production - many instances, many services, logs shipped to a central store - humans stop reading logs line by line. Machines do. Structured logging makes your logs something a machine can parse, index, and search.

From text lines to structured events

A traditional log line is a string: readable to you, opaque to tooling. A log aggregator (Elasticsearch/Kibana, Loki, Datadog) can only grep it. A structured log is a record with named fields - typically JSON:

{"timestamp":"2026-07-21T09:14:22Z","level":"ERROR","logger":"c.b.lending.LendingService",
 "message":"Borrow failed: no copies","traceId":"a1b2c3","memberId":42,"isbn":"978-0134685991"}

Now the aggregator can do what strings can't: 'show me every ERROR with isbn=978-0134685991 in the last hour', or 'group failures by memberId'. Spring Boot 3.4+ makes this a one-line config change - no custom appender:

logging:
  structured:
    format:
      console: ecs        # Elastic Common Schema JSON (or 'logstash', 'gelf')

Human-readable locally, structured in production

You don't want JSON scrolling past while developing. Enable structured output only in the profiles that ship (e.g. application-prod.yml), and keep the friendly, colored console format for local runs. Same code, different config - the twelve-factor way.

Correlation: tying lines to a request

A single request produces many log lines, scattered among lines from other concurrent requests. What ties its lines together is a value in the MDC (Mapped Diagnostic Context) - a per-thread (or per-request) bag of key/values automatically added to every line. The most important key is the trace ID:

[traceId=a1b2c3 spanId=d4e5]  Borrowing book 978-0134685991 for member 42
[traceId=a1b2c3 spanId=d4e5]  Loan created, 3 copies remaining

When Micrometer Tracing is present (the tracing lesson), Spring populates traceId/spanId in the MDC for you, so filtering logs by one trace ID reconstructs a request's whole story - even across services. You can add your own MDC keys (MDC.put("memberId", ...)) for domain context.

A library's catalog vs. a pile of books

A pile of books is 'searchable' only by picking each up and reading the spine. A catalog records each book as fields - title, author, subject, shelf - so you can query 'every book by this author on this subject' instantly. Plain text logs are the pile; structured logs are the catalog. Same books, but now findable.

Levels you can turn without redeploying

Logging frameworks have levels - ERROR, WARN, INFO, DEBUG, TRACE - and you set a threshold so noise stays out of production. The catch: when you need DEBUG to diagnose a live incident, redeploying to change it is slow. Actuator's loggers endpoint changes a level at runtime:

# turn on DEBUG for one package on a running instance - no restart
curl -X POST localhost:8080/actuator/loggers/com.bookvault.lending \
  -H 'Content-Type: application/json' -d '{"configuredLevel":"DEBUG"}'
Find one failing request in the noise

Production is serving 500 requests a second across three instances, all logging to one central store. One user reports their borrow failed. Explain how structured logging plus a trace ID lets you isolate exactly that request's log lines, when a plain-text grep for 'failed' would drown you.

Why is structured (JSON) logging with a trace ID valuable in production?

Key takeaways

  • In production, logs are read by machines (aggregators), so they must be machine-parseable - structured JSON, not free text.
  • Spring Boot enables structured logging (ECS/Logstash/GELF) via one property; keep human-readable format for local dev.
  • The MDC adds per-request context (especially traceId/spanId) to every line, so filtering by trace ID reconstructs a request's story - even across services.
  • Log levels (ERROR..TRACE) control noise; Actuator's loggers endpoint changes them at runtime without a redeploy.
  • Logs are the 'what happened' pillar of observability, complementing metrics and traces.
Was this lesson helpful?
Edit this page on GitHub