Plugins
Log4j plugin system is the de facto extension mechanism embraced by various Log4j Core components. Plugins make it possible for extensible components to receive feature implementations without any explicit links in between. It is analogous to a dependency injection framework, but curated for Log4j-specific needs.
Log4j plugin system is implemented by Log4j Core, the logging implementation. It is deliberately not a part of the Log4j API to keep the logging API footprint small. |
Did you know about Plugin reference, the documentation extracted from the source code of all predefined Log4j plugins? Like Javadoc, but specialized for plugins! |
In this section we will give an overview of the Log4j plugin system by answering certain questions:
Declaring plugins
A class can be declared as a plugin by adding a @Plugin
annotation, which is essentially composed of following attributes:
name
-
Name of the plugin. It is recommended to be distinct among plugins sharing the same
category
.name
matching is case-insensitive. category
(optional)-
A name used for grouping a set of plugins.
category
matching is case-sensitive. elementType
(deprecated)-
We don’t recommend the usage of
elementType
anymore. Existing usages are kept for backward compatibility reasons with the legacy configuration syntax:<appender type="ConsoleAppender"
.
See LowerLookup.java
(a lookup for lower-casing its input) for a simple example.
Click to read more on name collision and overriding an existing plugin
The name
attribute of plugins of a certain category
is recommended to be distinct and this matching is case-insensitive.
In case of a name collision, a warning will be emitted, and the plugin discovery order will determine the effective plugin.
For example, to override the File
plugin which is provided by the built-in File Appender, you would need to place your plugin in a JAR file in the classpath ahead of Log4j Core JAR.
In an OSGi environment, the order that bundles are scanned for plugins generally follows the same order that bundles were installed into the framework; see
getBundles()
and
SynchronousBundleListener
.
In short, name collisions are even more unpredictable in an OSGi environment.
Declaring plugins represented in a configuration file
If your plugin needs to be represented by an element in a configuration file (such as an appender, layout, logger, or filter), following requirements must be met:
-
The
category
attribute of the@Plugin
annotation must be set toNode.CATEGORY
(Core
) -
It must have a plugin factory
See JsonTemplateLayout.java
for an example and notice these details:
-
There are two plugin declarations:
JsonTemplateLayout
andJsonTemplateLayout.EventTemplateAdditionalField
-
Both plugin declarations
-
Set the
category
attribute toNode.CATEGORY
-
Provide a
@PluginBuilderFactory
-annotated static method
-
Declaring plugin factories
A plugin factory is responsible for
-
Creating an instance of the plugin
-
Receiving values (
Configuration
instance, configuration attributes, etc.) available in the context
Every plugin that needs to be represented by an element in a configuration file must declare a plugin factory using one of the following:
- a
@PluginFactory
-annotated static method -
What is expected to be received is modelled as method arguments. Intended for simple plugins that receive less than a handful of values.
See
CsvParameterLayout.java
for an example on@PluginFactory
usage. - a
@PluginBuilderFactory
-annotated static method of return typeBuilder<T>
-
What is expected to be received is modelled as fields of a builder class. Intended for more sophisticated wiring needs.
Click for advantages of builder class over factory method
-
Attribute names don’t need to be specified, if they match the field name
-
Default values can be specified in code rather than through an annotation. This also allows a runtime-calculated default value, which isn’t allowed in annotations.
-
Default values are specified via code rather than relying on reflection and injection, so they work programmatically as well as in a configuration file.
-
Adding new optional parameters doesn’t require existing programmatic configuration to be refactored.
-
Easier to write unit tests using builders rather than factory methods with optional parameters.
See
JsonTemplateLayout.java
for an example on@PluginBuilderFactory
usage. -
If a plugin class implements Collection
or Map
, then no factory method is used.
Instead, the class is instantiated using the default constructor, and all child configuration nodes are added to the Collection
or Map
.
Plugin factory attribute types
To allow the current Configuration
to populate the correct arguments for the @PluginFactory
-annotated method (or fields for the builder class), every argument to the method must be annotated using one of the following attribute types.
@PluginAliases
-
Identifies a list of aliases for a
@Plugin
,@PluginAttribute
, or@PluginBuilderAttribute
@PluginAttribute
-
Denotes a configuration element attribute. The parameter must be convertible from a
String
using aTypeConverter
. Most built-in types are already supported, but customTypeConverter
plugins may also be provided for more type support. Note thatPluginBuilderAttribute
can be used in builder class fields as an easier way to provide default values. @PluginConfiguration
-
The current
Configuration
object will be passed to the plugin as a parameter. -
@PluginElement
-
The parameter may represent a complex object that itself has parameters that can be configured. This also supports injecting an array of elements.
@PluginNode
-
The current
Node
being parsed will be passed to the plugin as a parameter. @PluginValue
-
The value of the current
Node
or its attribute namedvalue
.
Each attribute or element annotation must include the name that must be present in the configuration in order to match the configuration item to its respective parameter. For plugin builders, the names of the fields will be used by default if no name is specified in the annotation.
Plugin factory attribute type converters
TypeConverter
s are a certain group of plugins for converting String
s read from configuration file elements into the types used in plugin factory attributes.
Other plugins can already be injected via the @PluginElement
annotation; now, any type supported by TypeConverter
s can be used in a @PluginAttribute
-annotated factory attribute.
Conversion of enum types are supported on demand and do not require custom TypeConverter
s.
A large number of built-in Java classes (int
, long
, BigDecimal
, etc.) are already supported; see TypeConverters
for a more exhaustive listing.
You can create custom TypeConverter
s as follows:
-
Extend from the
TypeConverter
interface -
Set the
category
attribute of the@Plugin
annotation toTypeConverters.CATEGORY
(TypeConverter
). Unlike other plugins, the plugin name of aTypeConverter
is purely cosmetic. -
Have a default constructor
-
Optionally, extend from
Comparable<TypeConverter<?>>
, which will be used for determining the order in case of multipleTypeConverter
candidates for a certain type
See TypeConverters.java
for example implementations.
Plugin factory attribute validators
Plugin factory fields and parameters can be automatically validated at runtime using constraint validators inspired by Bean Validation.
The following annotations are bundled in Log4j, but custom ConstraintValidator
can be created as well.
@Required
-
This annotation validates that a value is non-empty. This covers a check for null as well as several other scenarios: empty
CharSequence
objects, empty arrays, emptyCollection
instances, and emptyMap
instances. @ValidHost
-
This annotation validates that a value corresponds to a valid host name. This uses the same validation as
InetAddress.getByName(String)
. @ValidPort
-
This annotation validates that a value corresponds to a valid port number between 0 and 65535.
Registering plugins
To properly work, each Log4j plugin needs:
-
To be registered in the Log4j Plugin Descriptor (i.e.,
Log4j2Plugins.dat
). This file is generated using thePluginProcessor
annotation processor at compile-time. -
(Optionally) To be registered in the GraalVM reachability metadata descriptor, which will allow the plugin to be used in GraalVM native applications. The
GraalVmProcessor
annotation processor creates such a file at compile-time.
The |
You need to configure your build tool as follows to use both plugin processors:
-
Maven
-
Gradle
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<executions>
<execution>
<id>generate-log4j-plugin-descriptor</id>
<goals>
<goal>compile</goal>
</goals>
<phase>process-classes</phase>
<configuration>
<proc>only</proc>
<annotationProcessorPaths>
<!-- Include `log4j-core` providing
1. `org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor` that generates `Log4j2Plugins.dat`
2. `org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor` that generates the GraalVM reachability metadata file -->
<path>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.24.3</version>
</path>
</annotationProcessorPaths>
<annotationProcessors>
<!-- Process sources using `PluginProcessor` to generate `Log4j2Plugins.dat` -->
<processor>org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor</processor>
<!-- Process sources using `GraalVmProcessor` to generate a GraalVM reachability metadata file -->
<processor>org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor</processor>
</annotationProcessors>
<compilerArgs>
<!-- Provide the project coordinates to `GraalVmProcessor`: -->
<arg>-Alog4j.graalvm.groupId=${project.groupId}</arg>
<arg>-Alog4j.graalvm.artifactId=${project.artifactId}</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
</plugin>
compileJava {
// Provide the project coordinates to the `GraalVmProcessor`:
options.compilerArgs << '-Alog4j.graalvm.groupId=org.example'
options.compilerArgs << '-Alog4j.graalvm.artifactId=example'
}
dependencies {
// Process sources using:
// * `PluginProcessor` to generate `Log4j2Plugins.dat`
// * `GraalVmProcessor` to generate a GraalVM reachability metadata file
annotationProcessor('org.apache.logging.log4j:log4j-core:2.24.3')
}
Discovering plugins
PluginManager
is responsible for discovering plugins and loading their descriptions.
It locates plugins by looking in following places in given order:
-
Plugin descriptor files on the classpath (using the class loader that loaded the
log4j-core
artifact). These files are generated automatically at compile-time by the Log4j plugin annotation processor. See Registering plugins for details. -
[OSGi only] Serialized plugin listing files in each active OSGi bundle. A
BundleListener
is added on activation to continue checking new bundles after Log4j Core has started. -
[Deprecated] A comma-separated list of packages specified by the
log4j.plugin.packages
system property -
[Deprecated] Packages passed to the static
PluginManager.addPackages()
method before Log4j configuration takes place -
[Deprecated] The
packages
attribute declared at the root element of your Log4j configuration file
Loading plugins
It is pretty common that a plugin uses other plugins; appenders accept layouts, some layouts accept key-value pairs, etc. You can do this as follows:
-
If your plugin has a plugin factory (i.e., it is represented by a configuration file element), you can use the
@PluginElement
annotation to receive other plugins. See@PluginElement("EventTemplateAdditionalField")
usage inJsonTemplateLayout.java
for an example. -
Otherwise, you can use
PluginUtil
, which is a convenient wrapper aroundPluginManager
, to discover and load plugins. SeeTemplateResolverFactories.java
for example usages.