Logging: Logback Defaults, Structured Output, and Container-Friendly Routing
|
This section documents Spring Boot 4.1.x and Spring Framework 7.0.x on the Java 17/21+ baseline, as described by the official Spring Boot reference documentation and each sub-project’s own site (Spring Data, Spring for Apache Kafka, Micrometer, Project Reactor, springdoc-openapi, Spring gRPC, and the others listed in this section’s Bibliography) — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Spring Boot and its ecosystem continue to evolve; the examples here target the current 4.1.x / 7.0.x releases. This section’s bibliography lists the reference material consulted while preparing these pages. |
Spring Boot wires up a working logging setup with zero configuration — console output, sensible defaults, and a full framework underneath for the moments that call for more. This page covers the default Logback setup and level configuration, structured JSON output with correlation IDs for container platforms, and the choice between logging to a file and logging to stdout.
Spring Boot’s default Logback setup
With spring-boot-starter (or any starter, since they all depend on it transitively) on the classpath, Spring
Boot auto-configures Logback as the logging implementation behind the
SLF4J facade. Application and library code should depend only on SLF4J’s Logger /
LoggerFactory, never on Logback types directly, so the underlying implementation stays swappable:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void placeOrder(String orderId) {
log.debug("Validating order {}", orderId);
try {
// ... processing ...
log.info("Order {} placed successfully", orderId);
} catch (Exception ex) {
log.error("Failed to place order {}", orderId, ex);
}
}
}
SLF4J’s {} placeholders defer argument formatting until the log statement is actually emitted, so a
log.debug(…) call at an inactive level costs almost nothing — the string is never built. Without any
logback.xml/logback-spring.xml on the classpath, Spring Boot applies its own default configuration: a
single CONSOLE appender writing to stdout with color-coded level names (when the terminal supports ANSI) and
a pattern that includes timestamp, level, PID, thread, logger name, and message. The root logger level defaults
to INFO. See
the Spring Boot logging reference for the
full list of defaults and every logging.* property.
Log levels and per-package configuration
Levels are set with logging.level.<logger-name>, where <logger-name> is a fully qualified package or class
name (or root for everything else). More specific entries override less specific ones, so a single package
can be turned up without touching the rest of the application:
logging:
level:
root: INFO
com.example.orders: DEBUG # this package and its sub-packages
com.example.orders.PaymentClient: TRACE # a single noisy class, temporarily
org.springframework.web: WARN # quiet down a chatty framework package
org.hibernate.SQL: DEBUG # log every SQL statement Hibernate issues
The five standard levels, from most to least verbose, are TRACE, DEBUG, INFO, WARN, and ERROR; OFF
disables a logger entirely. The same properties can also be set as environment variables
(LOGGING_LEVEL_COM_EXAMPLE_ORDERS=DEBUG) or JVM system properties, which is convenient for turning on debug
logging for one package in a running container without rebuilding an image. logging.group.* names a custom
group of loggers that share one level in a single line:
logging:
group:
tomcat: org.apache.catalina, org.apache.coyote, org.apache.tomcat
level:
tomcat: DEBUG
For anything beyond level thresholds — custom appenders, filters, per-environment configuration — drop a
logback-spring.xml on the classpath (the -spring suffix lets it use Spring profile expressions such as
<springProfile name="prod">, which a plain logback.xml cannot). See
the Spring Boot logging reference for the
property-to-appender mapping and the full list of supported logging systems (Logback, Log4j2, JUL).
Structured (JSON) logging for container platforms
Plain-text log lines are easy for a human to read in a terminal, but a log aggregator (Elasticsearch, Loki,
CloudWatch Logs, Datadog) has to parse them back apart with regular expressions, which is brittle whenever a
message contains a newline or the pattern changes. Structured logging emits each log event as one JSON object
instead, with a stable field per piece of information. Spring Boot 3.4+ supports this out of the box, with no
extra dependency, via logging.structured.format:
logging:
structured:
format:
console: ecs # Elastic Common Schema on the console
file: logstash # Logstash JSON format if also writing to a file
Built-in formats are ecs (Elastic Common Schema), logstash (Logstash’s JSON layout), and gelf (Graylog
Extended Log Format); a custom StructuredLogFormatter<ILoggingEvent> bean can also be registered for a
bespoke schema. Each emitted line becomes a self-contained JSON document, for example:
{
"@timestamp": "2026-01-15T10:23:41.512Z",
"log.level": "INFO",
"message": "Order 4821 placed successfully",
"service.name": "orders-service",
"process.thread.name": "http-nio-8080-exec-3",
"log.logger": "com.example.orders.OrderService",
"trace.id": "6f8a1c2e9b3d4f5a",
"transaction.id": "9b3d4f5a6f8a1c2e"
}
Correlation IDs and MDC
A single user request often fans out across several log statements, threads, or even services; a
correlation ID (also called a trace ID or request ID) tags every one of them so a log aggregator can group
them back together. SLF4J’s MDC (Mapped Diagnostic Context) is the standard mechanism: it is a per-thread
map of key/value pairs that, once populated, is automatically attached to every subsequent log statement on
that thread until it is cleared.
A servlet filter is a natural place to populate it, once per incoming request:
import jakarta.servlet.*;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.UUID;
@Component
public class CorrelationIdFilter implements Filter {
private static final String CORRELATION_ID_KEY = "correlationId";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String correlationId = UUID.randomUUID().toString();
try {
MDC.put(CORRELATION_ID_KEY, correlationId);
chain.doFilter(request, response);
} finally {
MDC.remove(CORRELATION_ID_KEY); // always clear -- threads are pooled and reused
}
}
}
If the project already depends on Micrometer Tracing (spring-boot-starter-actuator plus a tracer such as
Micrometer Tracing Bridge OpenTelemetry), Spring Boot populates traceId and spanId in the MDC
automatically for every request, so a custom filter like the one above is only needed for an extra,
application-defined correlation key. Once present in the MDC, a value can be referenced from a plain-text
Logback pattern with %X{correlationId} (the brace must be escaped as \{ when written in prose like this
sentence, but needs no escaping inside a [source,…] block):
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%X{correlationId}] %-5level %logger{36} - %msg%n</pattern>
With structured JSON logging, every MDC entry is included as its own field automatically — no pattern changes
needed, which is one more reason JSON output suits container platforms where logs are consumed by machines
rather than read directly off a terminal. See
the Spring Boot logging reference
(Structured Logging section) for the full list of built-in formats and how to add custom fields to every event
via logging.structured.ecs.service.name and related properties.
Routing logs to a file vs. stdout
By default Spring Boot logs only to the console. A file destination is added with logging.file.name (a
specific file path) or logging.file.path (a directory, using a default file name inside it) — setting either
one adds a FILE appender alongside the console one, it does not replace it:
logging:
file:
name: /var/log/orders-service/application.log
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 7 # keep 7 days of rolled-over files
total-size-cap: 100MB
logging.logback.rollingpolicy.* configures Logback’s size- and time-based rollover so the log file doesn’t
grow without bound, and is only meaningful once a file destination is set.
Why stdout is preferred under container orchestration
Under Docker, Kubernetes, or any comparable orchestrator, the recommended practice is to log to stdout/stderr only and let the platform take care of collection, rather than have the application write to a file at all:
-
The container filesystem is ephemeral — a restarted or rescheduled pod loses anything written to a file inside it, while stdout is captured by the container runtime for the life of the container and is available immediately even after a crash.
-
The orchestrator (via its container runtime’s logging driver, or a node-level agent such as Fluent Bit or Promtail) already tails stdout/stderr from every container and forwards it to a central aggregator — writing to a file as well means either a redundant second collection path or logs the aggregator never sees.
-
Multiple replicas of the same service each write their own file, so log rotation, disk-space management, and cross-instance searching all become the application’s problem instead of the platform’s.
-
Structured JSON on stdout (see above) is exactly the format most log-shipping agents expect, so the two practices reinforce each other.
A minimal, container-friendly configuration therefore sets no logging.file.* property at all and relies on
the default console appender, typically combined with structured output:
logging:
structured:
format:
console: ecs
level:
root: INFO
File-based logging remains the right choice for a traditionally deployed application on a long-lived VM or bare
metal, where logging.file.name plus rolling policies gives operators a persistent, locally inspectable log
history without depending on an external aggregator. See
the Spring Boot logging reference (File
Output section) for the complete set of logging.file. and logging.logback.rollingpolicy. properties.