JavaLogging

Logging

Every beginner uses System.out.println() to see what a program is doing, and it works fine for small scripts. But it falls apart quickly for anything running in production.
Warning
System.out.println() has no concept of severity, no way to filter messages, no way to redirect output to a file or a monitoring system, and no way to turn noisy messages off without editing and redeploying the code. In a real application these limitations matter a lot — you cannot ship debug-level detail to every user's console, and you cannot instantly see only the errors in a sea of routine output.
The Logging Facade Pattern
The standard solution is a dedicated logging library, and the standard way to use one is through a facade: a thin, stable API that your code depends on, backed by an actual logging implementation that can be swapped without touching your source code. SLF4J (Simple Logging Facade for Java) is the most common facade; a popular implementation behind it is Logback, though the JDK also ships a built-in logger (java.util.logging) that needs no extra dependency.

The benefit of the facade layer is that library authors can log against SLF4J without forcing every consumer of their library to use one specific logging backend — the application assembling everything picks the implementation once, at the very end.

Log Levels

Every log message is tagged with a severity level. The logging configuration then decides, per level, whether a message is printed, ignored, or sent somewhere specific — without changing a single line of application code.

Level

Typical Use

TRACE

Extremely fine-grained detail, rarely enabled outside deep debugging sessions

DEBUG

Diagnostic information useful during development, usually off in production

INFO

Routine, expected events worth recording — a service starting up, a request completing

WARN

Something unexpected happened but the application can continue

ERROR

A failure that likely needs attention — an operation could not complete

Using SLF4J

Logging with SLF4J's Logger

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

public class UserService {

    private static final Logger log = LoggerFactory.getLogger(UserService.class);

    public void login(String username) {
        log.info("User {} logged in", username);

        try {
            authenticate(username);
        } catch (AuthenticationException e) {
            log.error("Authentication failed for user {}", username, e);
        }
    }

    private void authenticate(String username) {
        // authentication logic
    }
}

A Logger is created once per class, typically as a private static final field, and named after that class so log output can be traced back to its source.

Tip
Use parameterized logging — log.info("User {} logged in", username) — instead of string concatenation like log.info("User " + username + " logged in"). With concatenation, Java builds the full string every single time the line runs, even if INFO logging is disabled. With the parameterized form, the logging framework only builds the final message if that level is actually enabled, which avoids wasted work in hot code paths.
  • Prefer a stable logging facade (SLF4J) over calling a specific implementation directly in application code

  • Configure log levels per environment — verbose in development, quieter in production

  • Always log the exception object itself (not just its message) so a full stack trace is captured

  • Never log sensitive data — passwords, tokens, full credit card numbers — even at DEBUG level

Note
Logging output can be filtered and routed (to a file, a rotating set of files, a centralized log aggregation system) purely through configuration — this is the whole point of separating log level and destination from the log statement itself.