Spring Boot: Logging with SLF4J & Logback

Spring Boot uses SLF4J as the logging facade and Logback as the default implementation, included automatically via spring-boot-starter-logging. No extra dependency or XML config is needed to get started: Spring Boot pre-configures a sensible console output with colour-coded log levels.

Contents

Default Logging Setup

Out of the box, Spring Boot logs to the console at INFO level with a pattern that includes date/time, log level, thread name, logger name, and message. No configuration is required.

  • Logging framework: Logback (via spring-boot-starter-logging)
  • Facade: SLF4J. Your code always uses SLF4J; the implementation can be swapped.
  • Default level: INFO for your application, WARN for most Spring framework classes.

SLF4J Logger Usage

Get one logger per class and use {} placeholders instead of string concatenation.

Java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    // Declare a logger per class — use the class itself as the logger name
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public Order placeOrder(OrderRequest request) {
        log.debug("Placing order for customer: {}", request.getEmail());

        Order order = orderRepository.save(new Order(request));

        log.info("Order placed successfully — orderId={}, customerId={}",
                 order.getId(), request.getCustomerId());

        return order;
    }

    public void processPayment(Long orderId) {
        try {
            paymentService.charge(orderId);
        } catch (PaymentException e) {
            log.error("Payment failed for orderId={}", orderId, e); // exception as last arg
        }
    }
}
Use parameterised logging ({} placeholders) rather than string concatenation. SLF4J only formats the message string if the log level is enabled, avoiding unnecessary object-to-string conversions.

Log Levels

LevelUse for
TRACEVery fine-grained diagnostics: SQL parameters, loop iterations
DEBUGDevelopment diagnostics: method entry/exit, variable values
INFONormal application flow: startup, request received, order placed
WARNUnexpected but recoverable situations: retry, fallback used
ERRORErrors that require attention: exceptions, failed operations

Levels are ordered: TRACE < DEBUG < INFO < WARN < ERROR. Setting a level to INFO means TRACE and DEBUG messages are suppressed.

Configuring Log Levels in application.properties

The settings below can be placed in application.yml or application.properties. All keys shown are optional unless marked: Spring Boot applies sensible defaults when they are omitted.

Properties
# Root log level
logging.level.root=WARN

# Your application packages
logging.level.com.example=DEBUG
logging.level.com.example.repository=TRACE

# Third-party libraries
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql=TRACE

# Write logs to a file
logging.file.name=logs/app.log

# Log pattern (console and file)
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

logback-spring.xml Customisation

Create src/main/resources/logback-spring.xml for full control over appenders, patterns, and rolling policies. Using the logback-spring.xml name (rather than logback.xml) allows Spring Boot profile support via <springProfile>.

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

  <!-- Console appender -->
  <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>

  <!-- Rolling file appender — daily rotation, keep 30 days -->
  <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.gz</fileNamePattern>
      <maxHistory>30</maxHistory>
      <totalSizeCap>1GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- Profile-specific configuration -->
  <springProfile name="dev">
    <root level="DEBUG">
      <appender-ref ref="CONSOLE" />
    </root>
  </springProfile>

  <springProfile name="prod">
    <logger name="com.example" level="INFO"/>
    <root level="WARN">
      <appender-ref ref="FILE" />
    </root>
  </springProfile>

</configuration>

MDC for Contextual Logging

MDC (Mapped Diagnostic Context) lets you attach key-value pairs to the current thread that are automatically included in every log message. It is ideal for adding request IDs, user IDs, or trace IDs to all logs within a request scope.

Java
import org.slf4j.MDC;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
public class MdcLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String requestId = Optional.ofNullable(request.getHeader("X-Request-ID"))
            .orElse(UUID.randomUUID().toString());

        try {
            MDC.put("requestId", requestId);
            MDC.put("method", request.getMethod());
            MDC.put("uri", request.getRequestURI());
            chain.doFilter(request, response);
        } finally {
            MDC.clear(); // always clean up to avoid thread-pool leakage
        }
    }
}
XML
<!-- Include MDC values in log pattern with %X{key} -->
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} [reqId=%X{requestId}] - %msg%n</pattern>
Always call MDC.clear() in a finally block. Thread pool threads are reused, failing to clear MDC will leak values into subsequent unrelated requests.