nirmalakumarsahu

Spring Boot Logging

πŸ“„ Articles πŸ‘€ My Profile

Spring Boot Spring Boot Logging

SLF4J Logback Log4j2

Spring Framework Framework


πŸ“‘ Index


πŸ“ What is Logging?

✨ Use-Cases of Logging

✨ Logging Frameworks

πŸ” Back to Top


πŸ”Ή SLF4J

Logging Frameworks

✨ Logging Levels in SLF4J

βœ… Best Practices / Tips

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.).

πŸ” Back to Top


πŸ“¦ Types of Appenders

πŸ’» ConsoleAppender

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

πŸ“„ FileAppender

πŸ“‚ RollingFileAppender βœ… (Most Common in Production)

<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>

πŸš€ AsyncAppender

<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE"/>
</appender>

🌐 SocketAppender

πŸ—„ DBAppender

🧩 SiftingAppender

πŸ›  Custom Appenders

πŸ”‘ Summary Table

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:

πŸ” Back to Top


βš™οΈ Logging in Spring Boot

Note:

πŸ–ŠοΈ Using SLF4J in Code

You typically declare a logger like this:

Option 1: Classic

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);
        }
    }
}

Option 2: Lombok

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");
    }
}

βš™οΈ Configuration

Default (no config file)

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

application.properties

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

logback-spring.xml (advanced)

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>

Best Practices 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>

πŸ“Š Advanced Features

Example with MDC:

import org.slf4j.MDC;

MDC.put("userId", "123");
log.info("Processing request");
// output: userId=123 Processing request
MDC.clear();

πŸ”„ How to Use Log4j2 Instead of Logback

Sometimes you want Log4j2 (better performance, async logging, advanced JSON layouts).

βœ… Steps:

Step 1: Exclude Logback

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>

Step 2: Add Log4j2 Dependencies

<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>

Step 3: Add log4j2-spring.xml

Place 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>

Step 4: Use Same SLF4J API or @Slf4j of Lombok

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.

πŸ”‘ Key Differences (Logback vs Log4j2 in Spring Boot)

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:

πŸ” Back to Top