DevNotes
Support
Logging (SLF4J & Log4j2) · Chapter 01

Introduction to Logging

Why professional applications log instead of using System.out.println, the evolution from JUL to SLF4J and Log4j2, loggers, appenders and layouts, the log levels and when to use each, and parameterized logging.

◷ 8 min read§ 15 sections? 8 interview questions{ } 16 code examples

#What Is Logging?

Logging is recording events that happen while a program runs. Instead of writing:

Java
System.out.println("User Logged In");

professional applications use a logger:

Java
logger.info("User Logged In");

because logs help you:

  • debug issues;
  • monitor production systems;
  • detect security problems;
  • analyse performance;
  • keep an audit history.

#Why Not System.out.println()?

Imagine a banking application with 5,000 Java classes. One transaction fails and the console shows:

Java
System.out.println("Transaction Failed");

Which class? Which method? Which thread? Which user? When? What exception? You don't know.

System.out.println()A logging framework
No levels — everything or nothingTRACE … ERROR, configurable per package
No timestamp, thread or classAdded automatically by the layout
Console onlyConsole, files, rolling files, remote log servers
Can't be turned off without code changesChanged in configuration, even at runtime
Synchronized and slow under loadBuffered and optionally asynchronous
No exception contextFull stack traces with logger.error("msg", e)

#Evolution of Java Logging

System.out.println()
java.util.logging (JUL) — built into the JDK
Apache Log4j 1.x — end of life since 2015
SLF4J — a logging facade (API)
Logback — the successor to Log4j 1.x
Log4j2 — a complete rewrite of Log4j

Today most Spring Boot applications use SLF4J + Logback (the Spring Boot default) or SLF4J + Log4j2.

Facade vs Implementation

LayerExamplesRole
Facade (API)SLF4JWhat your code calls — LoggerFactory.getLogger(...), logger.info(...)
ImplementationLogback, Log4j2, JULActually formats and writes the logs
Java
import org.slf4j.Logger;          // SLF4J — the facade
import org.slf4j.LoggerFactory;

Coding against SLF4J means you can switch from Logback to Log4j2 by changing dependencies and configuration, without touching a single Java class.

#What Is Log4j?

Log4j stands for Log for Java — an Apache framework for flexible, configurable logging. Its features include:

  • multiple log levels;
  • console, file, database, email and network logging;
  • rolling log files;
  • asynchronous logging;
  • pattern formatting with thread, class and timestamp information;
  • exception stack traces.

#Logging Architecture

Application calls logger.info()
Logger
Level check — is INFO enabled?
Appender — where the log goes
Layout — how the log looks
Destination — console, file, server

Examples:

Java class
Logger
ConsoleAppender
PatternLayout
Console
Logger
RollingFileAppender
PatternLayout
application.log

#Components

1. Logger

Creates log messages:

Java
private static final Logger logger = LoggerFactory.getLogger(EmployeeService.class);

2. Appender

Decides where logs go — console, file, rolling file, database, SMTP (email), socket, Kafka.

3. Layout

Decides how logs look:

Output
2026-07-23 10:25:10 INFO UserService User Created

4. Log Level

Decides which logs are written.

The Logger Flow

logger.info("Employee Saved")
Logger checks the configured level
Passes the event to its appenders
Each appender formats it with its layout
Log written

With the level set to INFO:

Output
2026-07-23 11:00:21 INFO EmployeeService Employee Saved

#Log Levels

From least to most severe:

TRACE
DEBUG
INFO
WARN
ERROR
FATAL

Setting a level enables that level and everything more severe: with INFO, the INFO, WARN, ERROR (and FATAL) messages are written; TRACE and DEBUG are skipped.

TRACE

The most detailed level — method entry and exit, variable values, loop iterations, SQL parameters.

Java
logger.trace("Entered calculateSalary()");

DEBUG

Diagnostic information used mainly during development and troubleshooting. Usually disabled in production.

Java
logger.debug("Employee ID = {}", id);

INFO

Normal, significant business events — user login, order created, email sent, account created.

Java
logger.info("Payment Completed");

WARN

Something unexpected happened, but the application still works — a slow database, a retry, missing optional configuration, low disk space.

Java
logger.warn("Password expires in 3 days");

ERROR

A failure the application couldn't handle normally — the database is down, an API call failed, an unexpected exception.

Java
try {
    // business logic
} catch (Exception e) {
    logger.error("Payment Failed", e);   // always pass the exception
}

Which Level Should I Use?

SituationLevel
Entering a method, loop detailsTRACE
Variable values, intermediate resultsDEBUG
User logged in, order placedINFO
Low memory, retrying a call, deprecated configWARN
Exception, failed operationERROR

#Logger Naming

The standard practice is one logger per class, named after the class:

Java
private static final Logger logger = LoggerFactory.getLogger(EmployeeService.class);

The logger's name is the fully qualified class name, e.g. com.example.service.EmployeeService — which lets you set levels per package. With Lombok, @Slf4j on the class generates the same field (named log).

#A Typical Spring Boot Example

Java
@RestController
public class EmployeeController {

    private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);

    private final EmployeeService employeeService;

    public EmployeeController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping("/employees")
    public List<Employee> getEmployees() {
        logger.info("Fetching Employees");
        return employeeService.getAll();
    }
}
Output
2026-07-23 10:10:55 INFO EmployeeController Fetching Employees

#A Banking Example

Java
logger.info("Transfer Started");
logger.debug("From Account={}", maskAccount(from));
logger.debug("To Account={}", maskAccount(to));
logger.debug("Amount={}", amount);

try {
    transfer();
    logger.info("Transfer Success");
} catch (Exception e) {
    logger.error("Transfer Failed", e);
}
With the level set to DEBUG
INFO  Transfer Started
DEBUG From Account=XXXX2345
DEBUG To Account=XXXX7890
DEBUG Amount=5000
INFO  Transfer Success

At the production level of INFO, only the two INFO lines appear. Account numbers are masked — full account numbers, card numbers and other personal data shouldn't appear in logs.

#Parameterized Logging

Java
// ✅ Parameterized — the message is only built if INFO is enabled
logger.info("User {} logged in from {}", userId, ipAddress);

// ❌ String concatenation — the string is built even if INFO is disabled
logger.info("User " + userId + " logged in from " + ipAddress);

{} placeholders avoid wasted work for disabled levels and keep messages consistent. Pass the exception as the last argument and it's logged with its stack trace:

Java
logger.error("Order {} failed", orderId, e);

For expensive arguments, guard the call or use a lambda (SLF4J 2 / Log4j2):

Java
if (logger.isDebugEnabled()) {
    logger.debug("State: {}", buildExpensiveReport());
}

#Interview Questions

Q1What is logging?

Recording application events for debugging, monitoring, auditing and troubleshooting.

Q2Why not use System.out.println()?

It has no log levels, timestamps, thread or class information, file output or configuration, can't be switched off per package, and doesn't work well in production.

Q3What are the main components of Log4j?

Logger, appender, layout and log level.

Q4What is the difference between a logger and an appender?

A logger creates log events; an appender decides where they're written.

Q5What is the difference between a layout and an appender?

The appender is the destination; the layout is the format.

Q6Which log levels are enabled in production?

Typically INFO, WARN and ERROR. DEBUG and TRACE are usually disabled, and switched on temporarily for a specific package when troubleshooting.

Q7What is the order of log levels?

TRACE < DEBUG < INFO < WARN < ERROR (< FATAL in Log4j2).

Q8What is SLF4J, and why use it?

The Simple Logging Facade for Java — an API that your code calls, while an implementation such as Logback or Log4j2 does the actual logging. It decouples your code from the implementation.

#Common Mistakes

  • Wrong level — logger.error("Employee Saved") (saving isn't an error) or logger.debug("Payment Failed") (a failure must be ERROR).
  • Logging the exception message without the exception — logger.error("Failed: " + e.getMessage()) loses the stack trace. Use logger.error("Failed", e).
  • String concatenation in log calls.
  • Logging sensitive data — passwords, OTPs, tokens, card or account numbers.
  • Logging and rethrowing the same exception at every layer — the same stack trace appears many times.

#Best Practices

  • INFO for important business events, DEBUG for developer diagnostics, TRACE only for detailed execution flow.
  • Always include the exception object in ERROR logs: logger.error("Unable to process payment", e).
  • Never log passwords, OTPs, tokens or card numbers.
  • Use parameterized logging with {}.
  • Code against SLF4J, one private static final logger per class.

#Key Takeaways

  • Logging frameworks add levels, context and configurable destinations that System.out lacks.
  • SLF4J is the facade; Logback (Spring Boot default) and Log4j2 are implementations.
  • A logger creates events, an appender sends them somewhere, a layout formats them.
  • Levels: TRACE < DEBUG < INFO < WARN < ERROR (< FATAL in Log4j2); production usually runs at INFO.
  • Use {} placeholders and pass exceptions as the last argument.