Simple Logging Facade for Java (SLF4J) API - a facade/abstraction layer for various logging frameworks.
SLF4J uses a service provider architecture that allows different logging framework implementations to be plugged in at runtime. The Service Provider Interface (SPI) defines the contracts that logging implementations must fulfill to integrate with SLF4J.
Main service provider interface for pluggable logging implementations.
/**
* This interface based on ServiceLoader paradigm. It replaces the old static-binding mechanism used in SLF4J versions 1.0.x to 1.7.x.
*/
public interface SLF4JServiceProvider {
/**
* Return the instance of ILoggerFactory that LoggerFactory class should bind to
* @return instance of ILoggerFactory
*/
ILoggerFactory getLoggerFactory();
/**
* Return the instance of IMarkerFactory that MarkerFactory class should bind to
* @return instance of IMarkerFactory
*/
IMarkerFactory getMarkerFactory();
/**
* Return the instance of MDCAdapter that MDC should bind to
* @return instance of MDCAdapter
*/
MDCAdapter getMDCAdapter();
/**
* Return the maximum API version for SLF4J that the logging implementation supports
* @return the string API version, for example "2.0.1"
*/
String getRequestedApiVersion();
/**
* Initialize the logging back-end
* WARNING: This method is intended to be called once by LoggerFactory class and from nowhere else
*/
void initialize();
}Factory interface for creating Logger instances.
/**
* ILoggerFactory instances manufacture Logger instances by name
*/
public interface ILoggerFactory {
/**
* Return an appropriate Logger instance as specified by the name parameter
* @param name the name of the Logger to return
* @return a Logger instance
*/
Logger getLogger(String name);
}Factory interface for creating and managing Marker instances.
/**
* Implementations of this interface are used to manufacture Marker instances
*/
public interface IMarkerFactory {
/**
* Manufacture a Marker instance by name. If the instance has been created earlier, return the previously created instance
* @param name the name of the marker to be created, null value is not allowed
* @return a Marker instance
*/
Marker getMarker(String name);
/**
* Checks if the marker with the name already exists
* @param name marker name
* @return true if the marker exists, false otherwise
*/
boolean exists(String name);
/**
* Detach an existing marker
* @param name The name of the marker to detach
* @return whether the marker could be detached or not
*/
boolean detachMarker(String name);
/**
* Create a marker which is detached from this IMarkerFactory
* @param name marker name
* @return a marker detached from this factory
*/
Marker getDetachedMarker(String name);
}How SLF4J discovers and loads service providers.
/**
* LoggerFactory methods for service provider management
*/
public final class LoggerFactory {
/**
* Explicitly set the provider class via system property
*/
public static final String PROVIDER_PROPERTY_KEY = "slf4j.provider";
/**
* Return the SLF4JServiceProvider in use
* @return provider in use
*/
static SLF4JServiceProvider getProvider();
/**
* Return the ILoggerFactory instance in use
* @return the ILoggerFactory instance in use
*/
public static ILoggerFactory getILoggerFactory();
}Usage Examples:
// Implementing a custom service provider
public class MyLoggingServiceProvider implements SLF4JServiceProvider {
private ILoggerFactory loggerFactory;
private IMarkerFactory markerFactory;
private MDCAdapter mdcAdapter;
public MyLoggingServiceProvider() {
// Initialize factories during construction
this.loggerFactory = new MyLoggerFactory();
this.markerFactory = new MyMarkerFactory();
this.mdcAdapter = new MyMDCAdapter();
}
@Override
public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}
@Override
public IMarkerFactory getMarkerFactory() {
return markerFactory;
}
@Override
public MDCAdapter getMDCAdapter() {
return mdcAdapter;
}
@Override
public String getRequestedApiVersion() {
return "2.0.17";
}
@Override
public void initialize() {
// Perform any necessary initialization
System.out.println("MyLoggingServiceProvider initialized");
}
}
// Custom logger factory implementation
public class MyLoggerFactory implements ILoggerFactory {
private final Map<String, Logger> loggerMap = new ConcurrentHashMap<>();
@Override
public Logger getLogger(String name) {
return loggerMap.computeIfAbsent(name, this::createLogger);
}
private Logger createLogger(String name) {
return new MyLogger(name);
}
}Extended logger interface that provides location information.
/**
* An optional interface helping integration with logging systems capable of extracting location information
*/
public interface LocationAwareLogger extends Logger {
// Level constants
public final static int TRACE_INT = 00;
public final static int DEBUG_INT = 10;
public final static int INFO_INT = 20;
public final static int WARN_INT = 30;
public final static int ERROR_INT = 40;
/**
* Printing method which can be used by implementation classes to receive location information
* @param marker The marker to be used for this event, may be null
* @param fqcn The fully qualified class name of the logger instance
* @param level One of the level integers defined in this interface
* @param message The message for the log event
* @param argArray An array of arguments to be used in conjunction with the message
* @param t The throwable associated with the log event, may be null
*/
public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t);
}Interface for loggers that support caller boundary detection.
/**
* This interface is used by LoggingEventBuilder implementations to determine the caller boundary
*/
public interface CallerBoundaryAware {
/**
* Return the caller boundary for this Logger
* @return the caller boundary. Null by default.
*/
default String getCallerBoundary() {
return null;
}
}Interface for loggers that can handle LoggingEvent objects directly.
/**
* Logger implementations which are capable of handling LoggingEvents should implement this interface
*/
public interface LoggingEventAware {
/**
* Log a LoggingEvent
* @param event the LoggingEvent to log
*/
void log(LoggingEvent event);
}Default implementations that perform no actual logging.
/**
* NOPLogger is an implementation of Logger that performs no operation
*/
public class NOPLogger implements Logger {
public String getName() { return "NOP"; }
public boolean isTraceEnabled() { return false; }
public void trace(String msg) { }
// ... all methods do nothing
}
/**
* NOPLoggerFactory is a factory for NOPLogger instances
*/
public class NOPLoggerFactory implements ILoggerFactory {
public Logger getLogger(String name) {
return NOPLogger.NOP_LOGGER;
}
}
/**
* This implementation ignores all MDC operations
*/
public class NOPMDCAdapter implements MDCAdapter {
public void put(String key, String val) { }
public String get(String key) { return null; }
public void remove(String key) { }
public void clear() { }
// ... all methods do nothing
}Simple working implementations for basic scenarios.
/**
* A simple implementation of MDC using InheritableThreadLocal
*/
public class BasicMDCAdapter implements MDCAdapter {
private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal =
new InheritableThreadLocal<Map<String, String>>();
public void put(String key, String val);
public String get(String key);
public void remove(String key);
public void clear();
public Map<String, String> getCopyOfContextMap();
public void setContextMap(Map<String, String> contextMap);
// ... implementation details
}
/**
* BasicMarker holds references to other markers
*/
public class BasicMarker implements Marker {
private final String name;
private final List<Marker> referenceList = new ArrayList<>();
BasicMarker(String name);
public String getName();
public void add(Marker reference);
public boolean remove(Marker reference);
// ... implementation details
}
/**
* BasicMarkerFactory is a simple implementation of IMarkerFactory
*/
public class BasicMarkerFactory implements IMarkerFactory {
private final Map<String, Marker> markerMap = new ConcurrentHashMap<>();
public Marker getMarker(String name);
public boolean exists(String name);
public Marker getDetachedMarker(String name);
}Temporary implementations used during SLF4J initialization.
/**
* A logger implementation which logs via a delegate logger but can also buffer logging events when the delegate is null
*/
public class SubstituteLogger implements Logger {
private final String name;
private volatile Logger _delegate;
private final Queue<SubstituteLoggingEvent> eventQueue = new LinkedBlockingQueue<>();
public SubstituteLogger(String name);
public String getName();
void setDelegate(Logger delegate);
// ... delegates to _delegate when available, queues events otherwise
}
/**
* SubstituteLoggerFactory creates SubstituteLogger instances
*/
public class SubstituteLoggerFactory implements ILoggerFactory {
private final Map<String, SubstituteLogger> loggers = new ConcurrentHashMap<>();
public Logger getLogger(String name);
public List<SubstituteLogger> getLoggers();
public void postInitialization();
public void clear();
}Register service providers using the standard Java ServiceLoader mechanism:
org.slf4j.spi.SLF4JServiceProvider in META-INF/services/# META-INF/services/org.slf4j.spi.SLF4JServiceProvider
com.example.logging.MyLoggingServiceProviderOverride automatic discovery using system property:
// Set system property to explicitly specify provider
System.setProperty("slf4j.provider", "com.example.logging.MyLoggingServiceProvider");
// Or via command line
// -Dslf4j.provider=com.example.logging.MyLoggingServiceProviderpublic class CustomServiceProvider implements SLF4JServiceProvider {
private static final String REQUESTED_API_VERSION = "2.0.17";
private final ILoggerFactory loggerFactory;
private final IMarkerFactory markerFactory;
private final MDCAdapter mdcAdapter;
public CustomServiceProvider() {
// Initialize all components in constructor
this.loggerFactory = new CustomLoggerFactory();
this.markerFactory = new CustomMarkerFactory();
this.mdcAdapter = new CustomMDCAdapter();
}
@Override
public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}
@Override
public IMarkerFactory getMarkerFactory() {
return markerFactory;
}
@Override
public MDCAdapter getMDCAdapter() {
return mdcAdapter;
}
@Override
public String getRequestedApiVersion() {
return REQUESTED_API_VERSION;
}
@Override
public void initialize() {
// Perform initialization tasks
// This method is called once by LoggerFactory
configureLogging();
validateConfiguration();
}
private void configureLogging() {
// Configure your logging backend
}
private void validateConfiguration() {
// Validate configuration and throw exceptions if invalid
}
}public class CustomLogger implements Logger, LocationAwareLogger, LoggingEventAware {
private final String name;
private final CustomLoggingBackend backend;
public CustomLogger(String name, CustomLoggingBackend backend) {
this.name = name;
this.backend = backend;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isDebugEnabled() {
return backend.isLevelEnabled(name, Level.DEBUG);
}
@Override
public void debug(String msg) {
if (isDebugEnabled()) {
backend.log(name, Level.DEBUG, msg, null, null);
}
}
@Override
public void debug(String format, Object arg) {
if (isDebugEnabled()) {
FormattingTuple ft = MessageFormatter.format(format, arg);
backend.log(name, Level.DEBUG, ft.getMessage(), null, ft.getThrowable());
}
}
// LocationAwareLogger implementation
@Override
public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t) {
if (backend.isLevelEnabled(name, Level.intToLevel(level))) {
backend.log(name, marker, fqcn, Level.intToLevel(level), message, argArray, t);
}
}
// LoggingEventAware implementation
@Override
public void log(LoggingEvent event) {
if (backend.isLevelEnabled(name, event.getLevel())) {
backend.log(event);
}
}
// ... implement all other Logger methods
}public class CustomMDCAdapter implements MDCAdapter {
private final ThreadLocal<Map<String, String>> threadLocalMap = new ThreadLocal<>();
private final ThreadLocal<Map<String, Deque<String>>> threadLocalDequeMap = new ThreadLocal<>();
@Override
public void put(String key, String val) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
Map<String, String> map = threadLocalMap.get();
if (map == null) {
map = new HashMap<>();
threadLocalMap.set(map);
}
map.put(key, val);
}
@Override
public String get(String key) {
Map<String, String> map = threadLocalMap.get();
return (map != null) ? map.get(key) : null;
}
@Override
public void remove(String key) {
Map<String, String> map = threadLocalMap.get();
if (map != null) {
map.remove(key);
if (map.isEmpty()) {
threadLocalMap.remove();
}
}
}
@Override
public void clear() {
threadLocalMap.remove();
threadLocalDequeMap.remove();
}
@Override
public void pushByKey(String key, String value) {
Map<String, Deque<String>> dequeMap = threadLocalDequeMap.get();
if (dequeMap == null) {
dequeMap = new HashMap<>();
threadLocalDequeMap.set(dequeMap);
}
Deque<String> deque = dequeMap.computeIfAbsent(key, k -> new ArrayDeque<>());
deque.push(value);
}
@Override
public String popByKey(String key) {
Map<String, Deque<String>> dequeMap = threadLocalDequeMap.get();
if (dequeMap != null) {
Deque<String> deque = dequeMap.get(key);
if (deque != null && !deque.isEmpty()) {
return deque.pop();
}
}
return null;
}
// ... implement remaining methods
}SLF4J service providers should declare their API compatibility:
@Override
public String getRequestedApiVersion() {
// Declare the SLF4J API version this provider supports
return "2.0.17";
}Version compatibility matrix:
When multiple providers are found on the classpath:
slf4j.provider system propertySLF4J 2.0 replaces the old static binding mechanism:
StaticLoggerBinder and StaticMarkerBinder classesSLF4JServiceProvider interface with ServiceLoaderSLF4JServiceProvider and register via ServiceLoaderInstall with Tessl CLI
npx tessl i tessl/maven-org-slf4j--slf4j-api