Web Analytics

Logging with SLF4J and Logback

Advanced ~15 min read

Logging is essential for monitoring, debugging, and auditing applications. Proper logging helps you understand what your application is doing without attaching a debugger.

Logging Frameworks

Framework Description
SLF4J Facade/API - use this in your code
Logback Implementation - fast, feature-rich (recommended)
Log4j2 Implementation - async logging, plugins
java.util.logging Built-in JDK logging (limited features)
Best Practice: Always code against SLF4J (the facade), then choose an implementation (Logback or Log4j2) at deployment time. This allows switching implementations without changing code.

Setup (Maven)

<!-- SLF4J API -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.9</version>
</dependency>

<!-- Logback implementation -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.11</version>
</dependency>

Basic Usage

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {
    // Create logger for this class
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    public User findById(Long id) {
        logger.debug("Looking up user with id: {}", id);

        User user = userRepository.findById(id);

        if (user == null) {
            logger.warn("User not found: {}", id);
            return null;
        }

        logger.info("Found user: {}", user.getEmail());
        return user;
    }

    public void processPayment(Order order) {
        logger.info("Processing payment for order: {}", order.getId());

        try {
            paymentGateway.charge(order);
            logger.info("Payment successful for order: {}", order.getId());
        } catch (PaymentException e) {
            logger.error("Payment failed for order: {}", order.getId(), e);
            throw e;
        }
    }
}

Log Levels

Level When to Use Example
ERROR Application errors, requires attention Database connection failed
WARN Potential problems, recoverable Retry attempt, deprecated API used
INFO Important business events User registered, order placed
DEBUG Detailed flow information Method entry/exit, variable values
TRACE Most detailed, rarely used Loop iterations, low-level details

Parameterized Logging

// BAD: String concatenation (always evaluated)
logger.debug("Processing user: " + user.getName() + " with id: " + user.getId());

// GOOD: Parameterized (only evaluated if level is enabled)
logger.debug("Processing user: {} with id: {}", user.getName(), user.getId());

// With exception (exception is always the last argument)
logger.error("Failed to process user: {}", userId, exception);

Logback Configuration

Create src/main/resources/logback.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- Console output -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File output with rotation -->
    <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/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Set log levels for specific packages -->
    <logger name="com.example.myapp" level="DEBUG" />
    <logger name="org.springframework" level="INFO" />
    <logger name="org.hibernate.SQL" level="DEBUG" />

    <!-- Root logger -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>

</configuration>

MDC (Mapped Diagnostic Context)

Add contextual information to all log messages in a thread:

import org.slf4j.MDC;

public class RequestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        try {
            // Add context that appears in all logs for this request
            MDC.put("requestId", UUID.randomUUID().toString());
            MDC.put("userId", getCurrentUserId());

            chain.doFilter(request, response);
        } finally {
            MDC.clear();  // Always clean up!
        }
    }
}

// In logback.xml, use %X{key} to include MDC values:
// <pattern>%d [%X{requestId}] [%X{userId}] %level %logger - %msg%n</pattern>

// Output: 2024-01-15 10:30:45 [abc-123] [user42] INFO UserService - User logged in

Logging Best Practices

What to Log

  • Application startup/shutdown
  • Configuration values (not secrets!)
  • Business events (orders, registrations)
  • Errors with full stack traces
  • Integration points (API calls, DB queries)

What NOT to Log

  • Passwords, tokens, API keys
  • Credit card numbers, SSNs
  • Personal data (GDPR compliance)
  • Large data structures (use DEBUG level)
// BAD: Logging sensitive data
logger.info("User login: {} with password: {}", username, password);

// GOOD: Mask sensitive data
logger.info("User login: {}", username);

// BAD: Logging large objects at INFO
logger.info("Response: {}", hugeJsonResponse);

// GOOD: Log at DEBUG, or log summary
logger.debug("Full response: {}", hugeJsonResponse);
logger.info("Received {} items", response.getItems().size());

Performance Tips

// Check level before expensive operations
if (logger.isDebugEnabled()) {
    logger.debug("Expensive computation result: {}", computeExpensiveValue());
}

// Use lazy evaluation (SLF4J 2.0+)
logger.atDebug()
    .addArgument(() -> expensiveComputation())
    .log("Result: {}");

Summary

  • Use SLF4J facade with Logback implementation
  • Use parameterized logging (not string concatenation)
  • Choose appropriate log levels for different messages
  • Configure file rotation to manage disk space
  • Use MDC for request tracking across logs
  • Never log passwords, tokens, or PII