Logging

Master structured logging in Spring Boot — Logback configuration, SLF4J, MDC for request tracing, log levels, and production logging best practices.

Last reviewed: January 2025

·Spring Boot 2.7Spring Boot 3.x
✅
Quick Understanding: Spring Boot uses Logback by default with SLF4J as the facade. Use @Slf4j (Lombok) or LoggerFactory.getLogger() to get a logger. Set log levels in application.properties: logging.level.com.company=DEBUG. In production, use structured JSON logging (Logstash encoder) so logs can be parsed by ELK/Splunk. Add MDC for request correlation IDs.

🔍 What Is It?

Spring Boot's logging stack:

  • →SLF4J — the logging facade (API you code against)
  • →Logback — the default implementation
  • →Log4j2 — alternative implementation (better async performance)
  • →Lombok @Slf4j — generates the logger field automatically

Log levels (in order of severity):

  • →TRACE — extremely detailed (method entry/exit)
  • →DEBUG — developer debugging
  • →INFO — significant events (startup, key operations)
  • →WARN — unexpected but recoverable situations
  • →ERROR — errors that need attention

MDC (Mapped Diagnostic Context):

Thread-local key-value store that Logback includes in every log line from that thread. Use for: request ID, user ID, trace ID, tenant ID.

🎯 Why Do We Need It?

Good logging is essential for production operations:

  • →Debugging production issues without attaching a debugger
  • →Tracing a request across multiple services
  • →Auditing user actions
  • →Alerting on error patterns
  • →Performance analysis

🏗️ What Happens in Real Projects?

Production logging setup:

  • →JSON structured logs sent to ELK (Elasticsearch/Logstash/Kibana) or Splunk
  • →MDC with requestId, userId, traceId on every log line
  • →ERROR logs trigger PagerDuty alerts
  • →Log levels changed at runtime via Actuator without restart

⚙️ How It Works

SLF4J is a facade — your code calls SLF4J API, which delegates to the actual implementation (Logback). This means you can switch from Logback to Log4j2 without changing any application code. Logback's PatternLayout or LogstashEncoder formats each log event into the output format.

💻 Example

Production Logging Setup — Structured JSON logging with MDC and request correlation.

java
@Service
@Slf4j  // Lombok generates: private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public class OrderService {

    public OrderDetailDto placeOrder(CreateOrderRequest request, String userId) {
        log.info("Placing order for user {} with {} items",
            userId, request.getItems().size());

        try {
            Order order = orderRepository.save(Order.from(request));
            log.info("Order {} created successfully for user {}",
                order.getOrderNumber(), userId);

            paymentService.charge(order);
            log.debug("Payment processed for order {}", order.getOrderNumber());

            return OrderDetailDto.from(order);

        } catch (PaymentException e) {
            log.error("Payment failed for order request from user {}: {}",
                userId, e.getMessage(), e);  // include exception for stack trace
            throw e;
        } catch (Exception e) {
            log.error("Unexpected error placing order for user {}", userId, e);
            throw new RuntimeException("Order placement failed", e);
        }
    }
}

🤔 Common Confusions

❓ Why use SLF4J instead of Log4j or Logback directly?

SLF4J is a facade — your code is decoupled from the implementation. You can switch from Logback to Log4j2 without changing a single line of application code. Libraries should always use SLF4J so applications can choose their logging implementation. Always code against SLF4J, never against Logback or Log4j directly.

❓ What is the performance impact of logging?

String concatenation in log statements is evaluated even if the log level is disabled. Use parameterized logging: log.debug("User {}", userId) instead of log.debug("User " + userId). The {} placeholder is only resolved if DEBUG is enabled. Alternatively, wrap with if (log.isDebugEnabled()) for expensive computations.

🚨 Common Production Issues

MDC not cleared after request — thread pool reuse causes MDC values from previous requests to leak

Logging sensitive data (passwords, tokens, PII) — never log passwords, tokens, or personal data

Log4Shell vulnerability (Log4j 2.x) — Spring Boot uses Logback by default, not affected; if using Log4j2, keep it updated

Verbose DEBUG logging in production causing performance issues and disk fill

✅ Best Practices

Use structured JSON logging in production for machine-parseable logs

Add MDC with requestId, userId, traceId on every request

Always clear MDC in a finally block

Use parameterized logging: log.info("User {}", id) not string concatenation

Never log passwords, tokens, or PII

Change log levels at runtime via Actuator: POST /actuator/loggers/com.company {"configuredLevel": "DEBUG"}

🏛️ Architect's Note

Senior Architect's Perspective

In microservices, distributed tracing (Micrometer Tracing / Zipkin / Jaeger) is more valuable than individual service logs. Distributed tracing adds a traceId that follows a request across all services. Every log line includes the traceId — you can see the complete journey of a request across 10 services in one query. Spring Boot 3.x includes Micrometer Tracing auto-configuration.

💬 Frequently Asked Questions

How do I change log levels at runtime without restarting?

Use Spring Boot Actuator: POST /actuator/loggers/com.company.orderservice with body {"configuredLevel": "DEBUG"}. This changes the log level immediately for that logger. Changes are lost on restart. For permanent changes, update application.properties and redeploy.

🎯 Key Takeaways

1SLF4J is the facade; Logback is the default implementation in Spring Boot
2Use @Slf4j (Lombok) to avoid boilerplate logger declarations
3Use parameterized logging: log.info("User {}", id) for performance
4MDC enables per-request context in all log lines — always clear in finally
5Use JSON structured logging in production for ELK/Splunk ingestion
6Change log levels at runtime via Actuator without restart

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Spring Boot references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Spring Boot learning path

Browse the full Spring Boot topic collection and learning order on SplashRide.

Spring Boot Interview Prep

Practice Spring Boot interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Profiles

Master Spring Profiles for environment-specific configuration — separating dev, test, staging, and production settings without code changes.

Actuator

Master Spring Boot Actuator — the production-ready features for health checks, metrics, info, and management endpoints that every enterprise Spring Boot application needs.