Foundation · Observe

SLF4J, Logback & Log Levels

Meet the logging facade every Spring class already uses, pick the right level per event, and stop paying for string building nobody reads.

Primary source: Spring Boot 3.5 Reference: Logging

20 min

Lesson 1 of 2

1d streak

The Architectural Question

Println spam has no levels, no timestamps beyond chance, and cannot be filtered or shipped—so real signals drown and noisy files block nothing.

What this guide proves

  • Call SLF4J loggers with parameterized messages
  • Map events to ERROR, WARN, INFO, and DEBUG consistently
  • Configure level per package in application.yml

The SLF4J Logging Facade & Why System.out is Forbidden

Logging in production enterprise applications is not simply about printing messages to a terminal; it is about emitting structured, filterable, thread-safe operational telemetry. Using System.out.println or System.err.println in enterprise Java is considered a critical code-quality defect for multiple reasons.

Architectural Boundary · Do Not Confuse

Concept A

System.out.println: Synchronous blocking standard output. Synchronizes on PrintStream monitor (severe thread contention). No timestamps, no thread IDs, no log levels, and cannot be dynamically silenced in production.

Concept B

SLF4J + Logback: leveled filtering (DEBUG/INFO/ERROR) with configurable appenders. Console and file appenders are synchronous unless an async appender is explicitly configured. Enriched with MDC context, thread names, timestamps, and routing to files, syslog, or JSON streams.

SLF4J (Simple Logging Facade for Java) acts as an abstraction layer (an API) decoupling application code from the underlying logging engine (Logback in Spring Boot defaults).

OrderService.java
package com.example.order;

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

@Service
public class OrderService {
    // Canonical logger declaration:
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public void processOrder(String orderId, long amountCents) {
        log.info("Processing order id={} amountCents={}", orderId, amountCents);
        try {
            // business logic
        } catch (Exception ex) {
            log.error("Failed to process order id={}: {}", orderId, ex.getMessage(), ex);
        }
    }
}

The Static Logger Pattern

Declare loggers as private static final Logger log = LoggerFactory.getLogger(TargetClass.class);. Making the logger static avoids allocating a new Logger reference for every instance created, saving memory across millions of domain objects.

Active Recall Checkpoint

What happens if you pass an Exception as the final argument in log.error("Failed: {}", ex.getMessage(), ex)?

Hint: How does SLF4J treat a Throwable when passed as the last parameter?

Show answer & explanation

SLF4J recognizes Throwable as a special argument and prints the full stack trace automatically, even if there is no corresponding {} placeholder for it.

Log Level Hierarchy & Parameterized Message Formatting

SLF4J defines five standard log levels ordered by increasing severity: TRACE < DEBUG < INFO < WARN < ERROR. Setting a log level instructs the logger to emit messages at that severity and above while discarding lower-severity entries.

Log LevelIntended Audience & PurposeProduction Recommendation
TRACEDeep diagnostic tracing (method entry/exit, raw wire bytes, ASM instructions)Disabled in production; enabled only for targeted troubleshooting
DEBUGDeveloper troubleshooting (internal state transitions, query parameters, branch decisions)Disabled in production; enabled per-package when investigating staging issues
INFOBusiness milestones (service startup, payment completed, batch job finished, order placed)Default production level for application packages
WARNUnexpected but recoverable conditions (slow external response, fallback cache used, rate limit approaching)Always enabled in production; triggers proactive monitoring alerts
ERRORCritical failures requiring developer or ops intervention (unhandled exceptions, database outage, data corruption)Always enabled in production; triggers pager alerts and Sentry issues

The String Concatenation Performance Bug

Never write log.debug("User " + user.getId() + " data: " + user.toHeavyString());. Even if DEBUG logging is completely disabled, the JVM still evaluates the string concatenation and calls toHeavyString(), wasting CPU and generating garbage collector pressure. Always use parameterized placeholders: log.debug("User {} data: {}", user.getId(), user);.

Active Recall Checkpoint

When should you wrap a log statement with if (log.isDebugEnabled())?

Hint: Think about when parameterized placeholders {} are not enough.

Show answer & explanation

Use if (log.isDebugEnabled()) when constructing the log arguments themselves is computationally expensive (such as serializing a large JSON payload or querying an in-memory graph) so the calculation is skipped when DEBUG is off.

Configuring Logback in Spring Boot: application.yml vs logback-spring.xml

Spring Boot provides seamless logging configuration directly inside application.yml for basic level overrides, file logging, and console formatting. For advanced enterprise setups (rolling file policies, custom appenders, syslog), use logback-spring.xml.

application.yml
logging:
  level:
    root: INFO
    org.springframework.web: WARN
    org.hibernate.SQL: DEBUG # Show SQL queries
    org.hibernate.orm.jdbc.bind: TRACE # Show SQL bound parameters
    com.example.order: DEBUG
  pattern:
    console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} : %m%n"
  file:
    name: "/var/log/order-service/application.log"

Why name the file logback-spring.xml instead of logback.xml? If you use logback.xml, Logback initializes before Spring Boot loads its Environment. By using logback-spring.xml, Spring Boot controls Logback initialization, allowing you to use <springProfile name="prod"> tags and read properties via <springProperty scope="context" name="appName" source="spring.application.name"/>.

Active Recall Checkpoint

Why should you avoid naming your configuration file logback.xml in a Spring Boot application?

Hint: Consider whether Spring Boot environment features and profile tags will work.

Show answer & explanation

Standard logback.xml loads too early in the JVM lifecycle before Spring Boot's environment and profile processing are ready. Using logback-spring.xml allows you to leverage <springProfile> and <springProperty> tags.

Deliberate Failure Lab: Missing Logs & Logging Loop Explosions

A common developer panic in staging or production is: "I added logging, but nothing shows up in the console or log file!" Check the logger level, the effective root and package configuration, the active appender, and the deployment sink before changing application code.

PaymentController.java
@GetMapping("/pay")
public String pay() {
    log.debug("Initiating payment calculation"); // If root is INFO and package level is unset, this is silently discarded!
    return "ok";
}

Diagnostic Verification: Dynamic Level Changes at Runtime

You do not need to restart your Spring Boot application to debug logging! If Spring Boot Actuator is present, you can inspect and change log levels dynamically via HTTP POST to /actuator/loggers/com.example.order with {"configuredLevel": "DEBUG"}.

Active Recall Checkpoint

How can you temporarily enable DEBUG logging for a single package in a running production Spring Boot app without a restart?

Hint: Which Actuator endpoint manages logger levels?

Show answer & explanation

Send an HTTP POST request to /actuator/loggers/<package-name> with body {"configuredLevel": "DEBUG"}. The change takes effect immediately in memory.

Working Implementation

Code Walkthrough

Convert System.out debugging into leveled logging and prove DEBUG disappears when prod sets info.

LeveledLoggingLab.java
package com.example.logging;

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

@Service
public class LeveledLoggingLab {
    private static final Logger log = LoggerFactory.getLogger(LeveledLoggingLab.class);

    public void processPayment(String orderId, long amountCents) {
        // TRACE: High-volume wire-level detail
        log.trace("Entering processPayment for orderId={}", orderId);

        // DEBUG: Internal state transitions and branch evaluations
        log.debug("Evaluating fraud check rules for orderId={} amount={}", orderId, amountCents);

        // INFO: Business milestones visible in production
        log.info("Payment initiated for orderId={} amountCents={}", orderId, amountCents);

        if (amountCents > 1_000_000) {
            // WARN: Recoverable abnormal condition
            log.warn("High-value transaction detected for orderId={}: requires manual review", orderId);
        }
    }
}
SLF4J parameterized placeholders ({}) avoid unnecessary String allocations when DEBUG is disabled.
Log levels strictly filter output: setting root to INFO completely eliminates DEBUG and TRACE performance costs.
Declare loggers as private static final to avoid per-instance memory allocation overhead.

Synthesis & Production Verification

Production Acceptance Criteria

  • Emit one message per level and explain the audience of each
  • Silence com.example debug output via configuration only
  • Show parameterized logging costs nothing when disabled

Architectural & Technical Review

Core Interview Questions

Q01Why is System.out.println forbidden in production enterprise Java applications?Show answer

System.out is synchronous and synchronizes on PrintStream, causing severe thread contention under load. It lacks timestamps, thread context, and log levels, and cannot be dynamically filtered or routed.

Q02What are the five standard SLF4J log levels and when should each be used?Show answer

TRACE for high-volume wire detail; DEBUG for troubleshooting internal logic; INFO for major business milestones; WARN for unexpected but recoverable events; ERROR for failures requiring immediate intervention.

Q03Why should logback-spring.xml be preferred over logback.xml in Spring Boot?Show answer

logback-spring.xml allows Spring Boot to initialize logging after environment resolution, enabling <springProfile> and <springProperty> tags to adapt logging per active profile.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Using System.out.println for application debugging — Blocks worker threads on I/O monitors, degrades performance, and cannot be filtered or redirected.
  • Pitfall: Evaluating heavy string concatenations inside disabled debug log statements — String concatenation executes regardless of log level; use SLF4J parameterized placeholders ({}) instead.

Primary Documentation & Specifications

Supplementary Module Resources