Fundamentals 11 min read

How SLF4J Uses the Adapter Pattern to Bridge Log4j – A Deep Source Code Walkthrough

This article explains the adapter pattern, illustrates its role in SLF4J by analyzing the Logger interface, Log4jLoggerAdapter, and related factory code, compares object and class adapters, and discusses the pattern's advantages and drawbacks in Java logging frameworks.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
How SLF4J Uses the Adapter Pattern to Bridge Log4j – A Deep Source Code Walkthrough
SLF4J日志源码分析第5篇,介绍适配器模式的实际运用案例。

What Is the Adapter Pattern?

The adapter pattern wraps an incompatible interface with a compatible one, allowing a client to use a unified API. It involves two roles: the adapter (often named with an "Adapter" suffix) and the adaptee (the original, incompatible class).

In SLF4J, the unified org.slf4j.Logger interface must work with various logging frameworks such as Log4j, whose logger class is org.apache.log4j.Logger. Because Log4j's logger does not implement the SLF4J interface, an adapter is required to bridge the gap.

Adapter Pattern Class Diagram (SLF4J Example)

The diagram shows three key classes: the SLF4J Logger interface, the Log4jLoggerAdapter class, and the Log4j Logger implementation.

When an application calls SLF4J’s Logger, the adapter forwards the call to the underlying Log4j logger, translating method signatures and log levels as needed.

SLF4J Core Interfaces

public interface Logger {
    String getName();
    void debug(String msg);
    void info(String msg);
    void warn(String msg);
    void error(String msg);
    // ... other methods omitted
}

The Log4j logger class (simplified) looks like this:

public class Logger extends Category {
    protected Logger(String name) { super(name); }
    public static Logger getLogger(String name) { return LogManager.getLogger(name); }
    public static Logger getLogger(Class clazz) { return LogManager.getLogger(clazz.getName()); }
    public static Logger getRootLogger() { return LogManager.getRootLogger(); }
    public static Logger getLogger(String name, LoggerFactory factory) { return LogManager.getLogger(name, factory); }
    public void trace(Object message) {
        if (!this.repository.isDisabled(5000)) {
            if (Level.TRACE.isGreaterOrEqual(this.getEffectiveLevel())) {
                this.forcedLog(FQCN, Level.TRACE, message, (Throwable) null);
            }
        }
    }
    // ... other methods omitted
}

Log4j’s core log method, which the adapter ultimately invokes, is:

public void log(String callerFQCN, Priority level, Object message, Throwable t) {
    if (!this.repository.isDisabled(level.level)) {
        if (level.isGreaterOrEqual(this.getEffectiveLevel())) {
            this.forcedLog(callerFQCN, level, message, t);
        }
    }
}

Log4jLoggerAdapter – The Object Adapter

public final class Log4jLoggerAdapter extends LegacyAbstractLogger implements LocationAwareLogger, Serializable {
    private static final long serialVersionUID = 6182834493563598289L;
    final transient org.apache.log4j.Logger logger;
    Log4jLoggerAdapter(org.apache.log4j.Logger logger) {
        this.logger = logger;
        this.name = logger.getName();
        traceCapable = isTraceCapable();
    }
    @Override
    public void log(Marker marker, String callerFQCN, int level, String msg, Object[] arguments, Throwable t) {
        Level log4jLevel = toLog4jLevel(level);
        NormalizedParameters np = NormalizedParameters.normalize(msg, arguments, t);
        String formattedMessage = MessageFormatter.basicArrayFormat(np.getMessage(), np.getArguments());
        logger.log(callerFQCN, log4jLevel, formattedMessage, np.getThrowable());
    }
    @Override
    protected void handleNormalizedLoggingCall(org.slf4j.event.Level level, Marker marker, String msg, Object[] arguments, Throwable throwable) {
        Level log4jLevel = toLog4jLevel(level.toInt());
        String formattedMessage = MessageFormatter.basicArrayFormat(msg, arguments);
        logger.log(getFullyQualifiedCallerName(), log4jLevel, formattedMessage, throwable);
    }
    // ... other methods omitted
}

The adapter receives a Log4j Logger instance via its constructor and delegates all SLF4J logging calls to Log4j after converting log levels and formatting the message.

Logger Retrieval – Log4jLoggerFactory

public Logger getLogger(String name) {
    Logger slf4jLogger = loggerMap.get(name);
    if (slf4jLogger != null) {
        return slf4jLogger;
    } else {
        org.apache.log4j.Logger log4jLogger;
        if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) {
            log4jLogger = LogManager.getRootLogger();
        } else {
            log4jLogger = LogManager.getLogger(name);
        }
        Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
        Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
        return oldInstance == null ? newInstance : oldInstance;
    }
}

Thus, obtaining an SLF4J logger actually creates a Log4jLoggerAdapter that wraps the corresponding Log4j logger.

Object vs. Class Adapter

SLF4J’s implementation is an object adapter: the adapter holds a reference to the adaptee (Log4j logger) and forwards calls. A class adapter would use inheritance to adapt the target class, which leads to tighter coupling.

Default Adapter Pattern

When an interface has many methods but a client only needs a subset, a default (or abstract) adapter can provide empty implementations for all methods, allowing subclasses to override only the needed ones. Since Java 8 introduced default methods in interfaces, this pattern is less necessary.

Pros and Cons of the Adapter Pattern

Reusability – existing classes can be reused without modification.

Transparency – client code works against a unified interface.

Extensibility – adapters can add extra behavior while delegating to the adaptee.

Decoupling – the target interface and adaptee are independent.

Open/Closed – new adapters can be added without changing existing code.

Cons: excessive adapters can clutter the system, and class adapters increase coupling due to inheritance.

Conclusion

By examining SLF4J’s source, we see a concrete object‑adapter implementation that enables a uniform logging API across different logging frameworks. The same principle appears throughout Spring and other Java libraries, where classes ending with "Adapter" typically embody this pattern.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavalogginglog4jslf4jAdapter Pattern
Senior Brother's Insights
Written by

Senior Brother's Insights

A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.