Logging
print("here") doesn't survive past your laptop. This module is how a real Spring Boot service tells you what it's doing once it's running somewhere you can't attach a debugger.
Learning objectives
- Beginner: Add a logger to a class and log at the correct level (debug/info/warn/error) for a given situation.
- Intermediate: Configure logging output format and level per package in application.properties.
- Advanced: Set up structured (JSON) logging with a correlation/trace ID so a single request can be followed across log lines.
◆ The problem
A print statement has no level (you can't turn off just the noisy ones), no timestamp, no indication of WHICH class or thread produced it, and nowhere to send it except stdout — useless once your service is running as one of twenty replicas in Kubernetes and you need to find the one line that matters.
Spring Boot uses SLF4J as a logging facade, backed by Logback by default. A facade means your code depends on a simple, stable logging API (Logger.info(...)), while the actual implementation underneath (Logback, or Log4j2 if you swap it in) can change without touching a single line of your business logic.
private static final Logger log = LoggerFactory.getLogger(BookService.class); log.info("Book saved: id={}", book.getId());
The {} placeholder is deliberate — it defers building the string until the logging framework confirms this log level is actually enabled, so an log.debug(...) call costs almost nothing when debug logging is off in production.
| Level | Use it for |
|---|---|
| ERROR | Something failed and a human should probably look at it soon (an exception that couldn't be handled cleanly). |
| WARN | Something unexpected happened but the request still completed (a fallback path was taken, a deprecated API was called). |
| INFO | A significant business event worth keeping a permanent record of (an order was placed, a user registered). |
| DEBUG | Detail useful while actively investigating a bug, too noisy to leave on in production by default. |
| TRACE | Extremely fine-grained detail (every SQL parameter, every filter step) — almost never enabled outside local debugging. |
▲ Pitfall
Logging every request at INFO feels helpful until your service is handling real traffic and INFO logs cost real storage/ingestion money, and the signal (an actual business event) drowns in noise (every request's routine detail). Reserve INFO for things a human genuinely wants a permanent record of.
💻 Code example
if (order.getTotal() > 10_000) { log.warn("Unusually large order: id={}, total={}", order.getId(), order.getTotal()); }
You rarely want ONE global logging level — you want your OWN code at DEBUG while you're actively working on it, but every third-party library's internals to stay quiet at WARN so they don't drown out what you actually care about.
logging.level.root=WARN logging.level.com.cracklab.bookservice=DEBUG logging.level.org.hibernate.SQL=DEBUG
That last line is a well-known trick: Hibernate logs every generated SQL statement at DEBUG under its own org.hibernate.SQL logger, independent of your application's own logging configuration — useful for the exact N+1-query debugging covered in Module 09, without needing to attach a profiler.
Plain-text logs are easy for a human to read one at a time, and painful for a machine to search across millions of lines from twenty service instances. Production systems instead emit JSON logs, where every field (level, timestamp, message, and custom fields like userId or orderId) is a real, queryable field in whatever log aggregation tool (Loki, ELK, Datadog) is ingesting them.
◆ Under the hood
A correlation ID (also called a trace ID) is a unique value generated once per incoming request and attached to every single log line produced while handling it — usually via SLF4J's MDC (Mapped Diagnostic Context), which is thread-local storage the logging framework automatically includes in every log statement on that thread. Without it, following one user's request across dozens of log lines interleaved with a hundred other concurrent requests is nearly impossible.
In a microservices system (Module 18), this same correlation ID gets passed along in HTTP headers from service to service, so one customer-facing request can be traced across every service it touched — the foundation the observability tooling in the Kafka & Microservices category builds on.
Want a visual for this concept?
Generate a diagram tailored to “Logging” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →