Logback Configuration in Spring Boot: Multi‑Env, Dynamic Levels, Async, Masking, MDC & Trace Integration
This guide walks through a complete Logback setup for Spring Boot, covering multi‑environment profiles, runtime log‑level changes via Actuator, async appender tuning, structured JSON output, sensitive‑data masking, MDC‑based distributed tracing, exception stack filtering, performance metrics and best‑practice recommendations.
Logback Core Configuration
Use logback-spring.xml together with Spring profiles to isolate logging settings per environment.
<configuration>
<!-- Development -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.example" level="TRACE" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
</springProfile>
<!-- Production -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="ELK"/>
</root>
<logger name="com.example.payment" level="WARN" additivity="false">
<appender-ref ref="PAYMENT_FILE"/>
</logger>
</springProfile>
</configuration>Dynamic Log Level Management
Spring Boot Actuator exposes /actuator/loggers to view and modify logger levels at runtime.
# application.yml
management:
endpoint:
loggers:
enabled: true
endpoints:
web:
exposure:
include: loggersTypical API calls: GET /actuator/loggers – list all loggers. GET /actuator/loggers/com.example.payment – fetch current level. POST /actuator/loggers/com.example.payment with {"configuredLevel":"DEBUG"} – change level. POST /actuator/loggers/com.example.payment with {"configuredLevel":null} – reset to default.
Async Logging Optimization
Configure AsyncAppender to move I/O off the application thread. Key parameters are queueSize, discardingThreshold, neverBlock and maxFlushTime.
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>4096</queueSize>
<discardingThreshold>819</discardingThreshold>
<neverBlock>true</neverBlock>
<maxFlushTime>1000</maxFlushTime>
<appender-ref ref="FILE"/>
</appender>Log Format and Rolling Policy
A fixed‑length pattern reduces parsing overhead.
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%5.5thread] %-5level %40.40logger{39} %4line : %msg%n</pattern>
</layout>
<charset>UTF-8</charset>
</encoder>Rolling policy uses SizeAndTimeBasedRollingPolicy with maxFileSize 256 MB, maxHistory 7 days, totalSizeCap 10 GB and immediateFlush set to false for production.
Structured Logging with Logstash Encoder
JSON output simplifies ingestion by log‑analysis platforms.
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/structured.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/structured-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>128MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app_name":"${spring.application.name}","env":"${spring.profiles.active}"}</customFields>
<timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
<includeMdc>true</includeMdc>
</encoder>
</appender>Sensitive Data Masking
A custom ClassicConverter masks credit‑card numbers, phone numbers and email addresses before they are written.
public class SensitiveDataConverter extends ClassicConverter {
private static final Pattern CREDIT_CARD_PATTERN = Pattern.compile("\\b(?:\\d[ -]*?){15,16}\\b");
private static final Pattern PHONE_PATTERN = Pattern.compile("(\\d{3})\\d{4}(\\d{4})");
private static final Pattern EMAIL_PATTERN = Pattern.compile("(?<=@)[^@]+(?=\\.[^.]+$)");
@Override
public String convert(ILoggingEvent event) {
String message = event.getFormattedMessage();
message = CREDIT_CARD_PATTERN.matcher(message).replaceAll("****-****-****-****");
message = PHONE_PATTERN.matcher(message).replaceAll("$1****$2");
message = EMAIL_PATTERN.matcher(message).replaceAll("***");
return message;
}
}Register the converter in logback-spring.xml and use %sensitive in the pattern.
MDC and Distributed Tracing
Trace identifiers are propagated via MDC so every log line carries the request context.
public class TraceIdInterceptor implements HandlerInterceptor {
private static final String TRACE_ID = "traceId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String traceId = request.getHeader("X-Trace-Id");
if (traceId == null || traceId.isEmpty()) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
MDC.put(TRACE_ID, traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
MDC.remove(TRACE_ID);
}
}For async tasks, a TaskDecorator copies the MDC map.
public class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}Exception Stack Enhancement
Filter noisy packages from stack traces and use a custom ThrowableConverter to keep only relevant frames.
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<pattern>%d{ISO8601} [%thread] %-5level %logger - %msg%n%ex{full, sun.reflect, org.springframework, org.apache.catalina, org.apache.coyote, org.apache.tomcat, java.lang.reflect.Method, com.netflix, feign, org.hibernate}</pattern>
</encoder>Performance Monitoring
Key metrics to watch:
Log write throughput (events/sec).
Async queue depth and discarding threshold.
Log file size growth rate.
Error‑log proportion.
Latency of critical operations.
Micrometer bridge example:
@Configuration
public class LoggingMetricsConfig {
@Bean
public MicrometerBridge micrometerBridge() {
return new MicrometerBridge();
}
}
public class MicrometerBridge implements LoggingEventAware {
private final Counter asyncQueueSize;
private final Timer logProcessingTime;
public MicrometerBridge() {
MeterRegistry registry = Metrics.globalRegistry;
this.asyncQueueSize = registry.counter("log.async.queue.size");
this.logProcessingTime = registry.timer("log.processing.time");
}
@Override
public void beforeLogEvent(ILoggingEvent event) {
// optional pre‑log metrics
}
@Override
public void afterLogEvent(ILoggingEvent event) {
logProcessingTime.record(event.getTimeStamp() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
}Prometheus alert for high error rate:
groups:
- name: logging-alerts
rules:
- alert: HighErrorRate
expr: sum(rate(logback_events_total{level="error"}[5m])) by (app) > 10
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.app }}"
description: "Error rate is {{ $value }} errors/sec"Best‑Practice Summary
Use logback-spring.xml with Spring profiles for environment isolation.
Enable AsyncAppender in production to avoid blocking the application thread.
Set log levels per environment (DEBUG for dev, INFO/WARN for prod).
Prefer JSON structured logs for downstream analysis platforms.
Implement a unified masking converter to protect sensitive data.
Propagate trace identifiers via MDC for full‑stack tracing.
Monitor key log‑system metrics and configure alerts.
Plan storage capacity based on observed log growth rates.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer1970
Formerly called 'Code to 35'. Add our main WeChat ID to access a wealth of shared resources (algorithms, interview prep, tech stacks: Java, Python, Go, big data). We mainly share serious development techniques, focusing on output-driven input. Occasionally we post life snippets and gossip. Our aim is to attract precise traffic and test advertising opportunities.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
