Skip to content

Logging — SLF4J, Logback, Log4j2, MDC, Structured Logging, and Log Levels

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Logging. We cover key concepts, practical examples, and best practices to help you master this topic.

Java logging frameworks capture runtime information for debugging, monitoring, and auditing, with SLF4J as the standard abstraction layer. Logging is the primary mechanism for understanding what your application does in production — without it, diagnosing failures requires reproducing the issue locally.

What You'll Learn

  • SLF4J: the logging facade
  • Logback: the most popular implementation
  • Log4j2: high-performance alternative
  • MDC: mapped diagnostic context for request tracing
  • Structured logging with JSON

Why It Matters

Good logging practices reduce mean-time-to-resolution (MTTR) for production incidents. Poor logging — no context, wrong level, too verbose — makes debugging harder. Understanding logging frameworks helps you choose and configure the right tools.

Real-World Use

Every application logs. Spring Boot uses Logback by default. Log aggregation tools (ELK, Splunk, Datadog) parse structured logs.


SLF4J — The Logging Facade

SLF4J is a facade — it provides a uniform API over multiple logging implementations:

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

public class UserService {
    private static final Logger log = LoggerFactory.getLogger(UserService.class);

    public void createUser(String name) {
        log.info("Creating user: {}", name);
        // SLF4J parameterized — avoids string concatenation
    }
}

Log Levels

log.trace("Detailed debugging — not used in production");
log.debug("Debug information — enabled in dev/test");
log.info("Notable events — user creation, login");
log.warn("Potentially harmful situations — low disk, deprecated API");
log.error("Errors — exceptions, failures that need investigation");

Parameterized Logging

Always use {} placeholders instead of string concatenation:

// GOOD — lazy evaluation
log.info("User {} logged in from {}", user.getId(), ipAddress);

// BAD — string concatenation happens even if log level is disabled
log.info("User " + user.getId() + " logged in from " + ipAddress);

Logback

Logback is the default SLF4J implementation in Spring Boot. Configuration goes in logback-spring.xml or logback.xml:

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

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

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

    <logger name="com.example" level="DEBUG"/>
</configuration>

Log4j2

Log4j2 is a high-performance alternative using log4j2.xml:

<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
        </Console>
        <RollingFile name="File" fileName="logs/app.log"
                     filePattern="logs/app-%d{yyyy-MM-dd}.log.gz">
            <PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File"/>
        </Root>
        <Logger name="com.example" level="DEBUG"/>
    </Loggers>
</Configuration>

MDC — Mapped Diagnostic Context

MDC stores contextual information per-thread, which is included in log output:

import org.slf4j.MDC;

public class RequestFilter {
    public void handleRequest(String requestId, String userId) {
        MDC.put("requestId", requestId);
        MDC.put("userId", userId);

        try {
            // All logs in this thread include requestId and userId
            log.info("Processing request");
        } finally {
            MDC.clear(); // Always clean up
        }
    }
}

Configure the pattern to include MDC values:

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{requestId}] - %msg%n</pattern>

Structured Logging

Structured logging outputs machine-parseable JSON instead of plain text:

<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

Maven dependency:

<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.4</version>
</dependency>

JSON output example:

{"@timestamp":"2026-06-28T14:30:00.000+00:00","level":"INFO","logger":"com.example.UserService","message":"Creating user: Alice","requestId":"abc-123"}

Common Mistakes

  1. Using System.out.println() in production. Logging frameworks provide levels, configuration, and file rotation. Never use System.out for logging.
  2. String concatenation in log messages. log.info("Value: " + x) always constructs the string, even if INFO is disabled. Use log.info("Value: {}", x).
  3. Logging exceptions without stack trace. log.error("Error: " + e.getMessage()) loses the stack trace. Always pass the exception as the last argument: log.error("Failed to Process", e).
  4. Too much logging at the wrong level. Use DEBUG for detailed diagnostics, INFO for important events, WARN for potential problems, ERROR for failures.
  5. Not cleaning up MDC. MDC values persist across requests in thread pools. Always clear MDC in a finally block.

Practice Questions

1. What is the difference between SLF4J and Logback?
SLF4J is a facade (interface). Logback is an implementation. You write code against SLF4J and can swap implementations without changing code.

2. Why use parameterized logging ({}) instead of string concatenation?
String concatenation executes even when the log level is disabled. Parameterized logging evaluates only when the level is enabled.

3. What is MDC used for?
MDC stores per-thread contextual information (request ID, user ID) that is included in every log message from that thread.

4. What is structured logging?
Outputting logs as JSON instead of plain text, making them machine-parseable by log aggregation tools (ELK, Datadog).

5. What are the five SLF4J log levels?
TRACE, DEBUG, INFO, WARN, ERROR.

Challenge Question:
Design a logging Strategy for a microservice that handles HTTP requests. Include: request ID in every log line (via MDC), structured JSON output, different log levels for different packages, and a rolling file appender with archival (30 days). Configure DEBUG for your code, INFO for framework code, and WARN for third-party libraries.

FAQ

What is the difference between Log4j and Logback?

Logback is the successor to Log4j 1.x. It is faster, has native SLF4J support, and automatic reloading. Log4j 2 is a separate, high-performance rewrite that also supports SLF4J.

{{< faq "How do I configure different log levels for different packages?" "Add package-specific loggers: <logger name="org.springframework" level="WARN"/> leaves Spring at WARN while your code logs at DEBUG." >}}

What is the difference between `log.error` and `log.warn`?

ERROR indicates a failure that needs investigation (exception, failed operation). WARN indicates a potential problem that is handled gracefully (deprecated API, slow query, fallback used).

How do I avoid logging sensitive data?

Use a custom encoder that redacts fields like password, creditCard, ssn. Or configure Logback's RegexFilter to mask patterns. Never log full credit card numbers or passwords.

What is the Logback `SiftingAppender`?

An appender that creates separate log files based on a runtime value (e.g., one file per user session or per request type). Uses MDC values to determine the output file.

Mini Project

Create a logging configuration project:

  1. Set up logback-spring.xml with console and rolling file appenders
  2. Configure MDC to include requestId and userId
  3. Add structured JSON logging with Logstash encoder
  4. Write a LoggingDemo class that logs messages at all levels
  5. Demonstrate MDC by creating a RequestContext filter that sets MDC values
  6. Show that log.info("Hello {}") is faster than log.info("Hello " + name)
  7. Configure different log levels for different packages
  8. Test that changing logback.xml reloads without restart (auto-scan)

What's Next

Logging is essential for observing behavior. Now it's time for concurrency — Lesson 51 introduces threads and Runnable, the Thread class, Runnable and Callable interfaces, thread states, and daemon threads.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro