| π Articles | π€ My Profile |
Logging is the process of recording information about the execution of an application.
It helps developers and system administrators to monitor, debug, and analyze the behavior of applications.
Logging captures which components (classes, methods, modules) were involved in execution, and possibly what data or context was present.

logger.debug("User {} logged in at {}", user, time); instead of string concatenation. It avoids creating string objects when that log level is disabled.Great question π β understanding appenders is the key to mastering Logback (and logging in general).
In Logback, an Appender is responsible for writing log events to a particular destination (console, file, DB, socket, etc.).
System.out (console).kubectl logs).Pros:
Cons:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
Supports:
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/archive/app.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
FILE, JSON).AsyncAppender.<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
</appender>
AppenderBase.| Appender | Destination | Best Use Case |
|---|---|---|
ConsoleAppender |
Console / stdout | Dev, Docker logs |
FileAppender |
Single file | Small apps only |
RollingFileAppender |
Rotating files | Production logging |
AsyncAppender |
Non-blocking wrapper | High-performance prod apps |
SocketAppender |
Remote server (TCP/UDP) | Live streaming to ELK/Splunk |
DBAppender |
Database table | Rare, audit logging |
SiftingAppender |
Per-user/tenant files | Multi-tenant apps |
CustomAppender |
Any destination | Cloud/Kafka/etc. |
β Best Practice for Production:
AsyncAppender) for file-based persistence.Note:
spring-boot-starter already includes: SLF4J API and Logback (default implementation).spring-boot-starter-log4j2.You typically declare a logger like this:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public void processData() {
log.info("Process started");
log.debug("Processing with parameters x={}, y={}", 10, 20);
try {
int result = 10 / 0;
} catch (Exception e) {
log.error("Error occurred: ", e);
}
}
}
If you use Lombok, just annotate with @Slf4j:
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class MyService {
public void processData() {
log.info("Process started");
}
}
Spring Boot logs to the console in INFO level with a nice format:
2025-09-21 10:23:45.123 INFO 12345 --- [main] c.e.demo.MyService : Process started
You can control logging easily:
# Set global log level
logging.level.root=INFO
# Package-specific logging
logging.level.com.example.demo=DEBUG
logging.level.org.springframework.web=ERROR
# Log file output
logging.file.name=app.log
logging.file.path=logs
# Pattern customization
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
For full control, create src/main/resources/logback-spring.xml:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<append>true</append>
<encoder>
<pattern>%d %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
logback-spring.xml<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- Import Spring Boot properties for dynamic values -->
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="./logs"/>
<springProperty scope="context" name="LOG_LEVEL" source="logging.level.root" defaultValue="INFO"/>
<!-- Define log pattern -->
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- Console appender (for local/dev environments) -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Rolling file appender (for prod/staging environments) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Daily rollover -->
<fileNamePattern>${LOG_PATH}/archive/${APP_NAME}.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<!-- Keep logs for 30 days -->
<maxHistory>30</maxHistory>
<!-- Limit total size -->
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Async wrapper to avoid blocking on I/O -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>5000</queueSize>
<discardingThreshold>0</discardingThreshold>
</appender>
<!-- Root logger -->
<root level="${LOG_LEVEL}">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</root>
<!-- Fine-grained logging control -->
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate.SQL" level="DEBUG"/> <!-- show SQL only if needed -->
<logger name="com.example" level="DEBUG"/> <!-- your application package -->
</configuration>
Example with MDC:
import org.slf4j.MDC;
MDC.put("userId", "123");
log.info("Processing request");
// output: userId=123 Processing request
MDC.clear();
Sometimes you want Log4j2 (better performance, async logging, advanced JSON layouts).
β Steps:
Remove default Logback dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- For JSON logging (optional, for ELK) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
log4j2-spring.xmlPlace inside src/main/resources/:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
<Property name="LOG_PATH">logs</Property>
</Properties>
<Appenders>
<!-- Console -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<!-- Rolling File -->
<RollingFile name="File" fileName="${LOG_PATH}/app.log"
filePattern="${LOG_PATH}/archive/app-%d{yyyy-MM-dd}.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
<Logger name="com.example" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
</Loggers>
</Configuration>
You donβt change your Java code:
private static final Logger log = LoggerFactory.getLogger(MyService.class);
if you are using Lombok, just keep @Slf4j.
β‘οΈ Behind the scenes, it will use Log4j2 instead of Logback.
| Feature | Logback (default) | Log4j2 |
|---|---|---|
| Default in Spring Boot | β Yes | β No |
| Performance | Good | β‘ Better (async by default) |
| JSON support | Via Logstash encoder | Native layouts |
| Config file | logback-spring.xml |
log4j2-spring.xml |
| Async logging | Needs AsyncAppender |
Built-in with LMAX Disruptor |
| Extensibility | High | Higher |
β Best Practice: