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.
#What Is Logging?
Logging is recording events that happen while a program runs. Instead of writing:
System.out.println("User Logged In");professional applications use a logger:
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:
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 nothing | TRACE … ERROR, configurable per package |
| No timestamp, thread or class | Added automatically by the layout |
| Console only | Console, files, rolling files, remote log servers |
| Can't be turned off without code changes | Changed in configuration, even at runtime |
| Synchronized and slow under load | Buffered and optionally asynchronous |
| No exception context | Full stack traces with logger.error("msg", e) |
#Evolution of Java Logging
Today most Spring Boot applications use SLF4J + Logback (the Spring Boot default) or SLF4J + Log4j2.
Facade vs Implementation
| Layer | Examples | Role |
|---|---|---|
| Facade (API) | SLF4J | What your code calls — LoggerFactory.getLogger(...), logger.info(...) |
| Implementation | Logback, Log4j2, JUL | Actually formats and writes the logs |
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
Examples:
#Components
1. Logger
Creates log messages:
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:
2026-07-23 10:25:10 INFO UserService User Created4. Log Level
Decides which logs are written.
The Logger Flow
With the level set to INFO:
2026-07-23 11:00:21 INFO EmployeeService Employee Saved#Log Levels
From least to most severe:
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.
logger.trace("Entered calculateSalary()");DEBUG
Diagnostic information used mainly during development and troubleshooting. Usually disabled in production.
logger.debug("Employee ID = {}", id);INFO
Normal, significant business events — user login, order created, email sent, account created.
logger.info("Payment Completed");WARN
Something unexpected happened, but the application still works — a slow database, a retry, missing optional configuration, low disk space.
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.
try {
// business logic
} catch (Exception e) {
logger.error("Payment Failed", e); // always pass the exception
}Which Level Should I Use?
| Situation | Level |
|---|---|
| Entering a method, loop details | TRACE |
| Variable values, intermediate results | DEBUG |
| User logged in, order placed | INFO |
| Low memory, retrying a call, deprecated config | WARN |
| Exception, failed operation | ERROR |
#Logger Naming
The standard practice is one logger per class, named after the class:
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
@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();
}
}2026-07-23 10:10:55 INFO EmployeeController Fetching Employees#A Banking Example
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);
}INFO Transfer Started
DEBUG From Account=XXXX2345
DEBUG To Account=XXXX7890
DEBUG Amount=5000
INFO Transfer SuccessAt 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
// ✅ 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:
logger.error("Order {} failed", orderId, e);For expensive arguments, guard the call or use a lambda (SLF4J 2 / Log4j2):
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) orlogger.debug("Payment Failed")(a failure must be ERROR). - Logging the exception message without the exception —
logger.error("Failed: " + e.getMessage())loses the stack trace. Uselogger.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 finallogger per class.
#Key Takeaways
- Logging frameworks add levels, context and configurable destinations that
System.outlacks. - 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.