A versatile, industrial-grade, and reference implementation of the Log4j API with rich components for various logging use cases.
—
Log4j Core provides an extensible plugin architecture that allows custom appenders, layouts, filters, and other components to be seamlessly integrated into the logging framework. The plugin system uses annotation-based registration and factory methods for component creation.
Core annotations for creating plugins that integrate with Log4j Core.
/**
* Marks a class as a Log4j plugin
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Plugin {
/**
* Plugin name used in configuration
* @return Plugin name
*/
String name();
/**
* Plugin category (e.g., "Core", "Lookup", "Converter")
* @return Plugin category
*/
String category();
/**
* Element type for configuration (e.g., "appender", "layout", "filter")
* @return Element type
*/
String elementType() default "";
/**
* Whether to print object representation
* @return Print object flag
*/
boolean printObject() default false;
/**
* Whether to defer children processing
* @return Defer children flag
*/
boolean deferChildren() default false;
}/**
* Marks a static method as the factory method for creating plugin instances
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PluginFactory {
// No attributes - just marks the factory method
}/**
* Marks a parameter as receiving an attribute value from configuration
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PluginAttribute {
/**
* Attribute name in configuration
* @return Attribute name (defaults to parameter name)
*/
String value() default "";
/**
* Default value if attribute not specified
* @return Default value
*/
String defaultValue() default "";
/**
* Whether this attribute contains sensitive information
* @return Sensitive flag
*/
boolean sensitive() default false;
}/**
* Marks a parameter as receiving a child element from configuration
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PluginElement {
/**
* Element name in configuration
* @return Element name (defaults to parameter name)
*/
String value() default "";
}/**
* Marks a parameter to receive the current Configuration instance
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PluginConfiguration {
// No attributes - injects current configuration
}/**
* Marks a parameter to receive the configuration Node
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PluginNode {
// No attributes - injects configuration node
}/**
* Marks a parameter to receive the LoggerContext instance
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface PluginLoggerContext {
// No attributes - injects logger context
}Utility classes for plugin management and discovery.
/**
* Manager for plugin discovery and instantiation
*/
public class PluginManager {
/**
* Create PluginManager for specific category
* @param category Plugin category to manage
*/
public PluginManager(String category);
/**
* Get plugin type by name
* @param name Plugin name
* @return PluginType instance or null
*/
public PluginType<?> getPluginType(String name);
/**
* Get all plugin types in this category
* @return Map of plugin names to types
*/
public Map<String, PluginType<?>> getPlugins();
/**
* Create plugin instance
* @param name Plugin name
* @param elementType Expected element type
* @param node Configuration node
* @param configuration Current configuration
* @return Plugin instance or null
*/
public Object createPluginObject(String name, String elementType, Node node, Configuration configuration);
}/**
* Represents a plugin type with metadata
* @param <T> Plugin class type
*/
public class PluginType<T> {
/**
* Get plugin class
* @return Plugin class
*/
public Class<T> getPluginClass();
/**
* Get plugin name
* @return Plugin name
*/
public String getKey();
/**
* Get element type
* @return Element type
*/
public String getElementType();
/**
* Get plugin category
* @return Plugin category
*/
public String getCategory();
/**
* Check if object should be printed
* @return Print object flag
*/
public boolean isObjectPrintable();
/**
* Check if children should be deferred
* @return Defer children flag
*/
public boolean isDeferChildren();
}/**
* Example custom appender plugin
*/
@Plugin(name = "MyCustom", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
public class MyCustomAppender extends AbstractAppender {
/**
* Private constructor - use factory method
*/
private MyCustomAppender(String name, Filter filter, Layout<? extends Serializable> layout,
boolean ignoreExceptions, String customAttribute) {
super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
// Initialize custom appender
}
/**
* Append log event to custom destination
* @param event LogEvent to append
*/
@Override
public void append(LogEvent event) {
// Custom append logic
byte[] data = getLayout().toByteArray(event);
// Write data to custom destination
}
/**
* Factory method for creating appender instances
* @param name Appender name from configuration
* @param layout Layout component from configuration
* @param filter Filter component from configuration
* @param customAttribute Custom attribute value
* @param ignoreExceptions Whether to ignore exceptions
* @param configuration Current configuration
* @return MyCustomAppender instance
*/
@PluginFactory
public static MyCustomAppender createAppender(
@PluginAttribute("name") String name,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginElement("Filter") Filter filter,
@PluginAttribute("customAttribute") String customAttribute,
@PluginAttribute(value = "ignoreExceptions", defaultValue = "true") boolean ignoreExceptions,
@PluginConfiguration Configuration configuration) {
if (name == null) {
LOGGER.error("No name provided for MyCustomAppender");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
return new MyCustomAppender(name, filter, layout, ignoreExceptions, customAttribute);
}
}/**
* Example custom layout plugin
*/
@Plugin(name = "MyCustomLayout", category = Core.CATEGORY_NAME, elementType = Layout.ELEMENT_TYPE)
public class MyCustomLayout extends AbstractStringLayout {
private final String prefix;
private final String suffix;
/**
* Private constructor - use factory method
*/
private MyCustomLayout(Charset charset, String prefix, String suffix) {
super(charset);
this.prefix = prefix != null ? prefix : "";
this.suffix = suffix != null ? suffix : "";
}
/**
* Format log event to string
* @param event LogEvent to format
* @return Formatted string
*/
@Override
public String toSerializable(LogEvent event) {
StringBuilder sb = new StringBuilder();
sb.append(prefix);
sb.append(event.getTimeMillis()).append(" | ");
sb.append(event.getLevel()).append(" | ");
sb.append(event.getLoggerName()).append(" | ");
sb.append(event.getMessage().getFormattedMessage());
sb.append(suffix);
sb.append(System.lineSeparator());
return sb.toString();
}
/**
* Factory method for creating layout instances
* @param charset Character encoding
* @param prefix Prefix string for each log entry
* @param suffix Suffix string for each log entry
* @return MyCustomLayout instance
*/
@PluginFactory
public static MyCustomLayout createLayout(
@PluginAttribute(value = "charset", defaultValue = "UTF-8") Charset charset,
@PluginAttribute("prefix") String prefix,
@PluginAttribute("suffix") String suffix) {
return new MyCustomLayout(charset, prefix, suffix);
}
}/**
* Example custom filter plugin
*/
@Plugin(name = "MyCustomFilter", category = Core.CATEGORY_NAME, elementType = Filter.ELEMENT_TYPE)
public class MyCustomFilter extends AbstractFilter {
private final String requiredProperty;
/**
* Private constructor - use factory method
*/
private MyCustomFilter(String requiredProperty, Result onMatch, Result onMismatch) {
super(onMatch, onMismatch);
this.requiredProperty = requiredProperty;
}
/**
* Filter log event based on custom criteria
* @param event LogEvent to filter
* @return Filter result
*/
@Override
public Result filter(LogEvent event) {
// Custom filtering logic
String propertyValue = event.getContextData().getValue(requiredProperty);
if (propertyValue != null && !propertyValue.isEmpty()) {
return onMatch;
}
return onMismatch;
}
/**
* Factory method for creating filter instances
* @param requiredProperty Property that must be present
* @param match Result when filter matches
* @param mismatch Result when filter doesn't match
* @return MyCustomFilter instance
*/
@PluginFactory
public static MyCustomFilter createFilter(
@PluginAttribute("requiredProperty") String requiredProperty,
@PluginAttribute(value = "onMatch", defaultValue = "NEUTRAL") Result match,
@PluginAttribute(value = "onMismatch", defaultValue = "DENY") Result mismatch) {
if (requiredProperty == null) {
LOGGER.error("requiredProperty is required for MyCustomFilter");
return null;
}
return new MyCustomFilter(requiredProperty, match, mismatch);
}
}/**
* Example custom lookup plugin for variable substitution
*/
@Plugin(name = "mycustom", category = StrLookup.CATEGORY)
public class MyCustomLookup implements StrLookup {
private static final Map<String, String> customData = new HashMap<>();
static {
customData.put("appname", "MyApplication");
customData.put("version", "1.0.0");
customData.put("environment", "production");
}
/**
* Look up value by key
* @param key Lookup key
* @return Value for key or null
*/
@Override
public String lookup(String key) {
return customData.get(key);
}
/**
* Look up value with LogEvent context
* @param event LogEvent for context
* @param key Lookup key
* @return Value for key or null
*/
@Override
public String lookup(LogEvent event, String key) {
// Can use log event for context-specific lookups
return lookup(key);
}
/**
* Factory method for creating lookup instances
* @return MyCustomLookup instance
*/
@PluginFactory
public static MyCustomLookup createLookup() {
return new MyCustomLookup();
}
}Once plugins are created, they can be used in configuration files:
<Configuration status="WARN" packages="com.example.plugins">
<Appenders>
<MyCustom name="Custom" customAttribute="value">
<MyCustomLayout prefix="[CUSTOM] " suffix=" [END]"/>
<MyCustomFilter requiredProperty="userId" onMatch="ACCEPT" onMismatch="DENY"/>
</MyCustom>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Custom"/>
</Root>
</Loggers>
</Configuration><Configuration status="WARN">
<Properties>
<Property name="filename">logs/${mycustom:appname}-${mycustom:environment}.log</Property>
</Properties>
<Appenders>
<File name="File" fileName="${filename}">
<PatternLayout pattern="%d %level %logger - %msg%n"/>
</File>
</Appenders>
</Configuration>Log4j automatically discovers plugins in:
org.apache.logging.log4j.core package (built-in plugins)packages attribute of Configurationlog4j2.packages// Programmatically add plugin packages
System.setProperty("log4j2.packages", "com.example.plugins,com.mycompany.logging");
// Or in configuration
ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
builder.setPackages("com.example.plugins");Standard plugin categories in Log4j Core:
Install with Tessl CLI
npx tessl i tessl/maven-org-apache-logging-log4j--log4j-core