Skip to Content
Course content

169: Logging with SLF4J and Logback

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was on call for a fintech app when a critical bug hit: payments were failing for about 5% of our users, but only on Tuesdays. I spent three hours staring at a production log that was essentially a wasteland of System.out.println statements. Some were missing, some were swallowed by the container, and others were just useless strings like "here 1" or "processing...". When I finally managed to swap in a proper logging framework and set the level to DEBUG, the culprit jumped out in seconds: a timezone mismatch in the payment gateway's API. That's the difference between a three-hour nightmare and a three-minute fix.

In Java, we don't just "print" things to the console. We use logging frameworks. Specifically, you'll see the combination of SLF4J and Logback almost everywhere. The key thing to understand here is that they aren't the same thing. SLF4J (Simple Logging Facade for Java) is an abstraction—a set of interfaces. Logback is the engine that actually does the work of writing the logs to a file or the console. By coding to the SLF4J interface, you can swap Logback for something else later without changing a single line of your business logic. It's the "Interface over Implementation" principle applied to your diagnostic data.

Plugging in the SLF4J Facade

To get started, you don't instantiate a logger with new. Instead, you use a LoggerFactory. I always recommend making your logger private static final so it's shared across all instances of the class and cannot be accidentally changed.

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

public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

    public void processOrder(String orderId, double amount) {
        logger.info("Processing order {} for amount ${}", orderId, amount);
        
        if (amount > 10000) {
            logger.warn("High-value order detected: {}. Flagging for manual review.", orderId);
        }
    }
}

Notice those curly braces {}? Those are placeholders. One of the biggest mistakes I see junior devs make is using string concatenation, like logger.debug("User " + user + " logged in"). The problem is that the JVM performs that string concatenation even if the log level is set to INFO and the DEBUG message is discarded. With placeholders, SLF4J only constructs the final string if the log level is actually enabled, which saves a surprising amount of CPU and memory in high-traffic apps.

Taming the Noise with Logback Configuration

Logback is configured via an XML file (usually logback.xml) placed in your resources folder. This is where you decide what actually gets recorded. You don't want DEBUG logs in production—they'll fill up your disks in minutes—but you definitely want them in your local dev environment.

You can define "Appenders" (where the logs go, like the Console or a File) and "Loggers" (which classes should log at which level). For example, you might want your own code to log at DEBUG level, but you want the Spring Framework or Hibernate logs to stay at WARN so they don't drown out your own messages.

  • TRACE: The most fine-grained information. Use this for "I am entering this loop" type of logs.
  • DEBUG: Useful for developers during debugging. Things like "Query returned 5 results."
  • INFO: High-level events. "Order #123 processed successfully."
  • WARN: Something unexpected happened, but the app is still running. "Connection timed out, retrying..."
  • ERROR: Something broke. "NullPointerException in PaymentGateway."

Managing Log Rotation and Retention

If you log everything to a single file, that file will eventually grow to several gigabytes and crash your server. I've seen this happen in the wild more than once. To prevent this, Logback provides a RollingFileAppender. This allows you to "roll" the log file based on time (daily) or size (e.g., every 10MB), and automatically delete files older than 30 days.

Setting this up in your logback.xml ensures that your production environment stays lean while still giving you a historical trail to follow when something inevitably goes wrong at 3:00 AM on a Tuesday.




📋 Practical Task

Exercise: Implementing a Rotating File Logger for a User Authentication System

You are tasked with adding professional logging to a UserAuthService class. Currently, the class uses System.out.println, which is unacceptable for production.

Your Requirements:

  • Add the SLF4J Logger to the UserAuthService class using the LoggerFactory.
  • Replace all System.out.println calls with appropriate SLF4J levels:
    • Use INFO for successful logins.
    • Use WARN for failed login attempts (incorrect password).
    • Use ERROR for system exceptions (e.g., database connection failure).
    • Use DEBUG to log the username being attempted before the validation logic runs.
  • Use parameterized logging (the {} syntax) for all variables; do not use string concatenation.
  • Create a logback.xml file in the resources folder that:
    • Sets the root logging level to INFO.
    • Adds a ConsoleAppender to output logs to the terminal.
    • Adds a RollingFileAppender that saves logs to logs/auth.log, rolls the file daily, and keeps a maximum of 7 days of history.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.