<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app":"${APP_NAME}"}</customFields>
</encoder>
</appender>
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="JSON"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
<logger name="com.example.demo" level="DEBUG"/>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate" level="WARN"/>
</configuration>
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
@Service
public class LoggingService {
private static final Logger logger = LoggerFactory.getLogger(LoggingService.class);
public void processUser(Long userId, String action) {
// Add context to all log statements in this thread
MDC.put("userId", userId.toString());
MDC.put("action", action);
try {
logger.debug("Starting user processing");
logger.info("Processing user with ID: {}", userId);
// Parameterized logging - efficient
logger.info("User {} performed action: {}", userId, action);
// Performance-conscious logging
if (logger.isDebugEnabled()) {
logger.debug("Expensive operation result: {}", expensiveOperation());
}
simulateWork();
logger.info("User processing completed successfully");
} catch (Exception e) {
logger.error("Error processing user {}: {}", userId, e.getMessage(), e);
} finally {
// Always clear MDC
MDC.clear();
}
}
private String expensiveOperation() {
// This only runs if DEBUG is enabled
return "expensive result";
}
private void simulateWork() {
logger.trace("Detailed trace information");
logger.debug("Debug information for developers");
logger.info("Informational message");
logger.warn("Warning: something unexpected but handled");
if (Math.random() < 0.1) {
logger.error("Error occurred in simulation");
}
}
public void demonstrateStructuredLogging() {
logger.info("Event occurred",
org.slf4j.event.KeyValuePair.pair("eventType", "USER_LOGIN"),
org.slf4j.event.KeyValuePair.pair("ipAddress", "192.168.1.1"),
org.slf4j.event.KeyValuePair.pair("success", true));
}
}
SLF4J provides a logging facade, Logback implements it efficiently. I use logger levels—TRACE, DEBUG, INFO, WARN, ERROR—to control verbosity. Parameterized logging with {} placeholders improves performance versus string concatenation. MDC (Mapped Diagnostic Context) adds contextual data like user IDs or request IDs to logs. Appenders direct output—console, files, rolling files, external systems. Patterns format log entries with timestamps, levels, thread names, class names. Async appenders improve throughput. Environment-specific configuration uses Spring profiles. Structured logging outputs JSON for centralized systems like ELK stack. Proper logging enables debugging, monitoring, and audit trails. I avoid logging sensitive data and use appropriate levels—DEBUG for development, INFO for production events, ERROR for failures.