The Apache Log4j implementation of java.util.logging providing JUL to Log4j bridge functionality
—
The logger adapter system provides a pluggable architecture for bridging JUL Logger instances to different Log4j implementations. It automatically selects the most appropriate adapter based on available Log4j components and supports custom adapter implementations.
Logger adapters bridge the gap between JUL's Logger interface and Log4j's logging infrastructure. The system provides:
AbstractLoggerAdapter (abstract base)
├── ApiLoggerAdapter (log4j-api only)
└── CoreLoggerAdapter (log4j-core available)The LogManager selects adapters in the following priority order:
log4j.jul.LoggerAdapter property is set// Selection logic
String customAdapterClass = PropertiesUtil.getProperties()
.getStringProperty(Constants.LOGGER_ADAPTOR_PROPERTY);
if (customAdapterClass != null) {
// Try to load custom adapter
adapter = LoaderUtil.newCheckedInstanceOf(customAdapterClass, AbstractLoggerAdapter.class);
} else {
// Default to ApiLoggerAdapter (CoreLoggerAdapter detection is automatic)
adapter = new ApiLoggerAdapter();
}Most applications use automatic adapter selection:
// Set LogManager - adapter selection is automatic
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
// Standard JUL usage - adapter works transparently
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("com.example");
logger.info("Message logged through selected adapter");// Configure custom adapter via system property
System.setProperty("log4j.jul.LoggerAdapter", "com.example.CustomLoggerAdapter");
// Or via log4j2.properties
// log4j.jul.LoggerAdapter=com.example.CustomLoggerAdapterpublic abstract class AbstractLoggerAdapter extends org.apache.logging.log4j.spi.AbstractLoggerAdapter<Logger> {
/**
* Gets the appropriate LoggerContext for the current execution context.
* Considers classloader dependency and caller class for context isolation.
*
* @return LoggerContext for creating loggers
*/
protected LoggerContext getContext();
/**
* Creates a new Logger instance for the given name and context.
* Implementation varies based on available Log4j components.
*
* @param name the logger name
* @param context the LoggerContext
* @return new Logger instance
*/
protected abstract Logger newLogger(String name, LoggerContext context);
}public class ApiLoggerAdapter extends AbstractLoggerAdapter {
/**
* Creates ApiLogger instances using only log4j-api components.
* Uses MessageFormatMessageFactory for JUL-style message formatting.
*
* @param name the logger name
* @param context the LoggerContext
* @return new ApiLogger instance
*/
protected Logger newLogger(String name, LoggerContext context);
}public class CoreLoggerAdapter extends AbstractLoggerAdapter {
/**
* Creates enhanced Logger instances when log4j-core is available.
* Returns CoreLogger for full functionality or ApiLogger as fallback.
*
* @param name the logger name
* @param context the LoggerContext
* @return new CoreLogger or ApiLogger instance
*/
protected Logger newLogger(String name, LoggerContext context);
}The ApiLogger provides JUL Logger implementation using only log4j-api:
// Created by ApiLoggerAdapter
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("example");
// Full JUL API support with some limitations
logger.info("Standard logging works");
logger.log(java.util.logging.Level.WARNING, "Parameterized message: {0}", value);
// Limitations due to log4j-api constraints
logger.setLevel(java.util.logging.Level.DEBUG); // Limited support
Logger parent = logger.getParent(); // May not be accurateApiLogger Features:
setLevel() and getParent() support due to API constraintsThe CoreLogger provides enhanced functionality when log4j-core is available:
// Created by CoreLoggerAdapter when log4j-core is present
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("example");
// Enhanced functionality with full JUL API support
logger.setLevel(java.util.logging.Level.DEBUG); // Full support
Logger parent = logger.getParent(); // Accurate parent relationships
logger.setFilter(customFilter); // Full filter supportCoreLogger Features:
The adapter system handles LoggerContext selection based on:
// Context selection logic in AbstractLoggerAdapter
protected LoggerContext getContext() {
return getContext(
LogManager.getFactory().isClassLoaderDependent()
? StackLocatorUtil.getCallerClass(java.util.logging.LogManager.class)
: null
);
}Different contexts allow for:
Create custom adapters for specialized requirements:
public class CustomLoggerAdapter extends AbstractLoggerAdapter {
@Override
protected Logger newLogger(String name, LoggerContext context) {
// Custom logger creation logic
ExtendedLogger log4jLogger = context.getLogger(name, customMessageFactory);
// Return custom logger implementation
return new CustomJulLogger(log4jLogger);
}
// Custom logger implementation
private static class CustomJulLogger extends java.util.logging.Logger {
private final ExtendedLogger delegate;
CustomJulLogger(ExtendedLogger delegate) {
super(delegate.getName(), null);
this.delegate = delegate;
}
@Override
public void info(String msg) {
// Custom logging behavior
delegate.info("[CUSTOM] " + msg);
}
// Implement other Logger methods...
}
}Custom adapters must:
AbstractLoggerAdapternewLogger(String, LoggerContext) methodtry {
adapter = LoaderUtil.newCheckedInstanceOf(className, AbstractLoggerAdapter.class);
LOGGER.info("Using custom LoggerAdapter [{}].", className);
} catch (Exception e) {
LOGGER.error("Specified LoggerAdapter [{}] is incompatible.", className, e);
// Falls back to default adapter
adapter = new ApiLoggerAdapter();
}All adapter components are fully thread-safe:
Install with Tessl CLI
npx tessl i tessl/maven-org-apache-logging-log4j--log4j-jul