2.10 Logging Strategy (SLF4J / Logback)
Spring Boot uses SLF4J as a facade and Logback as the default implementation. In production, configure levels, format, and request tracing (MDC).
Reference: Spring Boot 3.2.x (spring-boot-starter-logging includes Logback)
1. SLF4J and Logback
- SLF4J: Logging API (facade). Application code uses
Loggerfrom SLF4J. - Logback: SLF4J implementation. Spring Boot includes it via spring-boot-starter-logging, so no extra dependency is needed.
- logback-classic: SLF4J binding + Logback core. logback-core: Appenders, encoders, etc.
Code depends only on SLF4J; where and how log output is written is controlled by Logback configuration.
2. Using SLF4J
@Slf4j
@Service
public class OrderService {
public void create(OrderRequest req) {
log.info("Order create request: orderId={}", req.getOrderId());
log.debug("Saved: {}", order);
}
}
- Use placeholders to avoid string concatenation when the level is disabled.
3. application.yml
logging:
level:
root: INFO
com.example: DEBUG
org.hibernate.SQL: DEBUG
4. MDC for Request Tracing
MDC.put("requestId", requestId);
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
Add %X{requestId} in the Logback pattern.
5. Logback Configuration (logback-spring.xml)
Place the config at src/main/resources/logback-spring.xml (or logback.xml). Using logback-spring.xml enables Spring Boot extensions (springProfile, springProperty).
5.1 Structure
- <appender>: Where log output goes (console, file, socket).
- <logger>: Level and appenders for a package/class.
- <root>: Top-level logger; default level and appenders for all logs.
5.2 Console and Pattern
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %X{requestId} %-5level %logger{36} - %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
Common pattern tokens
| Token | Meaning |
|---|---|
| %d | Timestamp |
| %thread | Thread name |
| %X{key} | MDC value |
| %-5level | Log level (padded) |
| %logger36 | Logger name (max 36 chars) |
| %msg | Message |
| %n | Newline |
| %ex | Exception stack trace |
5.3 Logger Inheritance (Hierarchy & Additivity)
Logback loggers form a name-based hierarchy. Logger names usually match package/class (e.g. com.example.service.OrderService).
Hierarchy
- root (no name): Top-level parent of all loggers.
- com → child of root
- com.example → child of com
- com.example.service → child of com.example
- com.example.service.OrderService → child of com.example.service
Level inheritance: If a logger has no level set, it uses the parent’s level up to root.
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.example.service" level="DEBUG"/>
- Logs from
com.example.service.OrderServiceuse DEBUG (from com.example.service). - Logs from
com.example.controlleruse INFO (inherited from root).
additivity (Appender inheritance)
Default is additivity="true". If a child logger has an appender, its log events go to that appender and are passed up to ancestors’ appenders, so the same log can appear in root’s CONSOLE as well (duplicate output).
additivity="false": Events handled by this logger are not passed to the parent. Use when you want a package to log only to a dedicated file and not to root (e.g. CONSOLE).
<logger name="com.example.service" level="DEBUG" additivity="false">
<appender-ref ref="SERVICE_FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
- Logs from
com.example.servicego only to SERVICE_FILE, not to CONSOLE. - Logs from
com.example.controller(additivity true by default) go only to root → CONSOLE.
Summary: Level is determined by the nearest ancestor with a level. Appenders: with additivity="true", events go to this logger’s appenders and all ancestors’ appenders. Set additivity="false" to avoid duplicates when using package-specific appenders.
5.4 Logging Configuration Options Reference
Quick reference for common Logback and application.yml options.
Log Levels
| Level | Meaning | When to use |
|---|---|---|
| TRACE | Most verbose | Flow tracing, early debugging (rarely in production) |
| DEBUG | Debug info | Development: SQL, parameters, internal state |
| INFO | General info | Request/response, business events (default in production) |
| WARN | Warning | Recoverable issues, deprecation usage |
| ERROR | Error | Exceptions, failures, unrecoverable state |
Only events at or above the configured level are logged. level="INFO" logs INFO, WARN, ERROR and drops DEBUG and TRACE.
ConsoleAppender options
| Option | Description | Example |
|---|---|---|
| encoder | Format (pattern, charset) | See 5.2 |
| pattern | Conversion pattern | %d{yyyy-MM-dd HH:mm:ss} %-5level %msg%n |
| charset | Encoding | UTF-8 |
| target | Output. System.out (default) / System.err | System.err |
RollingFileAppender options
| Option | Description | Example |
|---|---|---|
| file | Current log file path | ${LOG_PATH}/app.log |
| encoder | Format (pattern, charset) | Same as console |
| rollingPolicy | Rotation policy (see below) | SizeAndTimeBasedRollingPolicy |
| append | Append to existing file (default true) | true |
SizeAndTimeBasedRollingPolicy options
| Option | Description | Example |
|---|---|---|
| fileNamePattern | Rolled file name. Use %d{...} (date), %i (index) | app-%d{yyyy-MM-dd}.%i.log.gz |
| maxFileSize | Max size per file before roll | 100MB, 500MB |
| maxHistory | Number of rolled files to keep (days when date-based) | 30 |
| totalSizeCap | Total size cap; oldest files deleted when exceeded | 3GB |
| cleanHistoryOnStart | Delete old files on startup (default false) | true |
TimeBasedRollingPolicy
Date-only rotation. Use %d{yyyy-MM-dd} in fileNamePattern and maxHistory for retention. No maxFileSize.
Encoder (PatternLayoutEncoder) options
| Option | Description | Example |
|---|---|---|
| pattern | Conversion pattern | %d %level [%thread] %logger - %msg%n |
| charset | Encoding | UTF-8 |
| immediateFlush | Flush on each log (default true). false = buffer | true |
Pattern conversion words (additional)
| Token | Description |
|---|---|
| %d{format} | Date. e.g. yyyy-MM-dd HH:mm:ss.SSS, ISO8601 |
| %relative | Milliseconds since start |
| %level / %-5level | Level (number = min width, - = left align) |
| %logger{length} | Logger name. %logger{36} = abbreviate FQCN to 36 chars |
| %class / %method / %line | Caller class/method/line (costly) |
| %ex / %throwable | Exception stack. %ex{short} = one-line summary |
| %X{key} | MDC value. %X = all MDC |
AsyncAppender options
| Option | Description | Default | Note |
|---|---|---|---|
| queueSize | Queue capacity | 256 | When full, discardingThreshold applies |
| discardingThreshold | When queue below this %, drop TRACE/DEBUG/INFO. 0 = drop any | 20 | 0 can drop all |
| includeCallerData | Include caller (class/line). Costly when async | false | Use only if needed |
| neverBlock | When queue full: drop (true) vs block (false) | false | true = drop |
| appender-ref | Delegate appender | — | Single ref only |
application.yml — logging options
| Key | Description | Example |
|---|---|---|
| logging.level.root | Root logger level | INFO |
| logging.level.<package> | Package-level level | com.example: DEBUG |
| logging.file.name | Log file path (full) | logs/app.log |
| logging.file.path | Log directory (file name = spring.log) | logs |
| logging.pattern.console | Console pattern | %d %-5level %logger{36} - %msg%n |
| logging.pattern.file | File pattern | Same |
| logging.pattern.dateformat | Default %d date format | yyyy-MM-dd HH:mm:ss.SSS |
When logback-spring.xml exists, its appenders and patterns take precedence over logging.pattern / logging.file. Many setups use yml only for levels and XML for patterns and appenders.
6. RollingFileAppender (File Rotation)
In production, logs are often written to files with size- and time-based rotation.
<property name="LOG_PATH" value="logs"/>
<property name="LOG_FILE" value="application"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
- SizeAndTimeBasedRollingPolicy: New file when date or size (e.g. 100MB) changes; %i for same-day index.
- maxHistory: Days to keep (30).
- totalSizeCap: Total size limit for all rolled files.
7. Profile-Specific Config (springProfile)
Use springProfile to switch appenders/levels per environment.
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.example" level="INFO"/>
</springProfile>
8. springProperty — Use application.yml
Read values from application.yml for paths and file names.
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="logs"/>
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="app"/>
9. AsyncAppender
Use AsyncAppender to buffer log events and write asynchronously, reducing latency on the request thread.
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
</root>
- queueSize: Queue size; when full, discardingThreshold controls how much to drop (TRACE/DEBUG first when > 0).
- appender-ref: The actual appender that writes (e.g. FILE).
10. JSON Logs (Production Aggregation)
For ELK, CloudWatch, Datadog, one JSON object per line is easier to parse. Use logstash-logback-encoder for JSON output.
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app-json.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-json-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>requestId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
</encoder>
</appender>
- LogstashEncoder: Outputs timestamp, level, logger, message, exception, and MDC as JSON.
- includeMdcKeyName: Include MDC keys as JSON fields for search (e.g. requestId, userId).
11. MDC in Practice
- Request ID: Set in filter/interceptor; MDC.clear() in finally.
- User ID: Set after authentication.
- Child threads (@Async, Executor): MDC is not inherited; use a TaskDecorator to copy parent MDC.
12. Production Notes
- Do not log passwords, tokens, or card numbers; mask if needed.
- Limit stack traces; use file rotation and maxHistory / totalSizeCap.
- JSON format helps with ELK/CloudWatch.
- With AsyncAppender, tune queueSize and discardingThreshold so logs are not dropped under load.
Use DEBUG for SQL in development; keep root at INFO in production. You can control console, file, JSON, and profiles entirely in logback-spring.xml.