Extending Log4j

Log4j 2 提供了许多可操作和扩展的方式。本节概述了 Log4j 2 实现直接支持的各种方式。

LoggerContextFactory

LoggerContextFactory 将 Log4j API 绑定到其实现。 Log4j LogManager 通过使用 java.util.ServiceLoader 查找 org.apache.logging.log4j.spi.Provider 的所有实例来查找 LoggerContextFactory。每个实现都必须提供一个 extendsorg.apache.logging.log4j.spi.Provider 的类,并应具有一个无参数的构造函数,该构造函数将通过 Priority 与其兼容的 API 版本以及实现 org 的类委托给 Provider 的构造函数。 apache.logging.log4j.spi.LoggerContextFactory。 Log4j 将比较当前的 API 版本,如果兼容,则将实现添加到提供程序列表中。 org.apache.logging.log4j.LogManager 中的 API 版本仅在将功能添加到实现需要注意的 API 时才更改。如果找到多个有效实施,则优先级的值将用于标识优先级最高的工厂。最后,将实例化实现 org.apache.logging.log4j.spi.LoggerContextFactory 的类并将其绑定到 LogManager。在 Log4j 2 中,这是由 Log4jContextFactory 提供的。

应用程序可能会更改将由 LoggerContextFactory 使用的 LoggerContextFactory。

  • 创建到日志记录实现的绑定。

  • 实现一个新的 LoggerContextFactory。

    • 实现一个扩展 org.apache.logging.spi.Provider 的类。使用无参数构造函数,该构造函数使用 Priority,API 版本,LoggerContextFactory 类以及可选的 ThreadContextMap 实现类调用超类的构造函数。

    • 创建一个 META-INF/services/org.apache.logging.spi.Provider 文件,其中包含实现 org.apache.logging.spi.Provider 的类的名称。

  • 将系统属性 log4j2.loggerContextFactory 设置为要使用的 LoggerContextFactory 类的名称。

  • 将名为“ log4j2.LogManager.properties”的属性文件中的属性“ log4j2.loggerContextFactory”设置为要使用的 LoggerContextFactory 类的名称。属性文件必须在 Classpath 上。

ContextSelector

ContextSelector 由Log4j LoggerContext 工厂调用。他们执行查找或创建 LoggerContext 的实际工作,这是 Logger 及其配置的基础。 ContextSelector 可以自由地实现他们希望 ManagementLoggerContext 的任何机制。默认的 Log4jContextFactory 检查是否存在名为“ Log4jContextSelector”的系统属性。如果找到,则该属性应包含实现要使用的 ContextSelector 的 Class 的名称。

Log4j 提供了五个 ContextSelector:

  • BasicContextSelector

    • 使用已存储在 ThreadLocal 中的 LoggerContext 或公共 LoggerContext。
  • ClassLoaderContextSelector

    • 将 LoggerContexts 与创建 getLogger 调用的调用程序的 ClassLoader 关联。这是默认的 ContextSelector。
  • JndiContextSelector

    • 通过查询 JNDI 找到 LoggerContext。
  • AsyncLoggerContextSelector

    • 创建一个 LoggerContext,以确保所有 Logger 都是 AsyncLoggers。
  • BundleContextSelector

    • 将 LoggerContexts 与创建 getLogger 调用的调用程序的包的 ClassLoader 关联。在 OSGi 环境中,默认情况下启用此功能。

ConfigurationFactory

修改日志记录的配置方式通常是最受关注的领域之一。这样做的主要方法是实现或扩展ConfigurationFactory。 Log4j 提供了两种添加新 ConfigurationFactories 的方式。第一种是通过将名为“ log4j.configurationFactory”的系统属性定义为应首先在其中搜索配置的类的名称。第二种方法是将 ConfigurationFactory 定义为插件。

然后按 Sequences 处理所有 ConfigurationFactories。每个工厂都会在其 getSupportedTypes 方法上被调用以确定其支持的文件 extensions。如果使用指定的文件 extensions 之一找到配置文件,则将控制传递给该 ConfigurationFactory 来加载配置并创建 Configuration 对象。

大多数配置扩展了 BaseConfiguration 类。此类期望子类处理配置文件并创建 Node 对象的层次结构。每个节点非常简单,因为它由节点名称,与该节点关联的名称/值对,该节点的 PluginType 以及其所有子节点的列表组成。然后,BaseConfiguration 将被传递给 Node 树,并从中实例化配置对象。

@Plugin(name = "XMLConfigurationFactory", category = "ConfigurationFactory")
@Order(5)
public class XMLConfigurationFactory extends ConfigurationFactory {

    /**
     * Valid file extensions for XML files.
     */
    public static final String[] SUFFIXES = new String[] {".xml", "*"};

    /**
     * Returns the Configuration.
     * @param loggerContext The logger context.
     * @param source The InputSource.
     * @return The Configuration.
     */
    @Override
    public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
        return new XmlConfiguration(loggerContext, source);
    }

    /**
     * Returns the file suffixes for XML files.
     * @return An array of File extensions.
     */
    public String[] getSupportedTypes() {
        return SUFFIXES;
    }
}

LoggerConfig

LoggerConfig 对象是应用程序创建的 Logger 与配置绑定的地方。 Log4j 实现要求所有 LoggerConfig 都基于 LoggerConfig 类,因此希望进行更改的应用程序必须通过扩展 LoggerConfig 类来进行更改。要声明新的 LoggerConfig,请将其声明为“ Core”类型的插件,并提供应用程序应在配置中指定为元素名称的名称。 LoggerConfig 还应该定义一个 PluginFactory,它将创建 LoggerConfig 的实例。

以下示例显示了根 LoggerConfig 如何简单地扩展通用 LoggerConfig。

@Plugin(name = "root", category = "Core", printObject = true)
public static class RootLogger extends LoggerConfig {

    @PluginFactory
    public static LoggerConfig createLogger(@PluginAttribute(value = "additivity", defaultBooleanValue = true) boolean additivity,
                                            @PluginAttribute(value = "level", defaultStringValue = "ERROR") Level level,
                                            @PluginElement("AppenderRef") AppenderRef[] refs,
                                            @PluginElement("Filters") Filter filter) {
        List<AppenderRef> appenderRefs = Arrays.asList(refs);
        return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, level, additivity);
    }
}

LogEventFactory

LogEventFactory 用于生成 LogEvent。应用程序可以通过将系统属性 Log4jLogEventFactory 的值设置为自定义 LogEventFactory 类的名称来替换标准 LogEventFactory。

注意:如果将 log4j 配置为具有所有 Logger 都是异步的,则将日志事件预先分配在环形缓冲区中,并且不使用 LogEventFactory。

MessageFactory

MessageFactory 用于生成 Message 对象。通过将系统属性 log4j2.messageFactory 的值设置为自定义 MessageFactory 类的名称,应用程序可以替换标准的 ParameterizedMessageFactory(或无垃圾模式下的 ReusableMessageFactory)。

Logger.entry()和 Logger.exit()方法的流消息具有单独的 FlowMessageFactory。应用程序可以通过将系统属性 log4j2.flowMessageFactory 的值设置为自定义 FlowMessageFactory 类的名称来替换 DefaultFlowMessageFactory。

Lookups

查找是执行参数替换的方式。在配置初始化期间,将创建一个“插值器”,该插值器查找所有查找并注册它们以供需要解析变量时使用。插值器将变量名称的“前缀”部分与已注册的 Lookup 匹配,并将控制权传递给它以解析变量。

必须使用类型为“查找”的插件 Comments 声明查找。插件 Comments 上指定的名称将用于匹配前缀。与其他插件不同,查找不使用 PluginFactory。相反,它们需要提供不接受任何参数的构造函数。下面的示例显示了一个查找,它将返回系统属性的值。

提供的查找记录在这里:Lookups

@Plugin(name = "sys", category = "Lookup")
public class SystemPropertiesLookup implements StrLookup {

    /**
     * Lookup the value for the key.
     * @param key  the key to be looked up, may be null
     * @return The value for the key.
     */
    public String lookup(String key) {
        return System.getProperty(key);
    }

    /**
     * Lookup the value for the key using the data in the LogEvent.
     * @param event The current LogEvent.
     * @param key  the key to be looked up, may be null
     * @return The value associated with the key.
     */
    public String lookup(LogEvent event, String key) {
        return System.getProperty(key);
    }
}

Filters

可以预期,过滤器用于拒绝或接受通过日志系统的日志事件。使用“ Core”类型的 Plugin 注解和“ filter”的 elementType 声明一个 Filter。插件 Comments 上的 name 属性用于指定用户启用过滤器时应使用的元素的名称。将 printObject 属性指定为“ true”值表示对 toString 的调用将在处理配置时格式化过滤器的参数。筛选器还必须指定一个 PluginFactory 方法,该方法将被调用以创建筛选器。

下面的示例显示了一个过滤器,该过滤器用于根据日志事件的日志记录级别拒绝它们。请注意典型的模式,其中所有过滤器方法都解析为一个过滤器方法。

@Plugin(name = "ThresholdFilter", category = "Core", elementType = "filter", printObject = true)
public final class ThresholdFilter extends AbstractFilter {

    private final Level level;

    private ThresholdFilter(Level level, Result onMatch, Result onMismatch) {
        super(onMatch, onMismatch);
        this.level = level;
    }

    public Result filter(Logger logger, Level level, Marker marker, String msg, Object[] params) {
        return filter(level);
    }

    public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
        return filter(level);
    }

    public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
        return filter(level);
    }

    @Override
    public Result filter(LogEvent event) {
        return filter(event.getLevel());
    }

    private Result filter(Level level) {
        return level.isAtLeastAsSpecificAs(this.level) ? onMatch : onMismatch;
    }

    @Override
    public String toString() {
        return level.toString();
    }

    /**
     * Create a ThresholdFilter.
     * @param loggerLevel The log Level.
     * @param match The action to take on a match.
     * @param mismatch The action to take on a mismatch.
     * @return The created ThresholdFilter.
     */
    @PluginFactory
    public static ThresholdFilter createFilter(@PluginAttribute(value = "level", defaultStringValue = "ERROR") Level level,
                                               @PluginAttribute(value = "onMatch", defaultStringValue = "NEUTRAL") Result onMatch,
                                               @PluginAttribute(value = "onMismatch", defaultStringValue = "DENY") Result onMismatch) {
        return new ThresholdFilter(level, onMatch, onMismatch);
    }
}

Appenders

向发布者传递事件,(通常)调用布局来格式化事件,然后以所需的任何方式“发布”事件。将 Appender 声明为插件,其类型为“ Core”,而 elementType 为“ appender”。插件 Comments 上的 name 属性指定用户在使用 Appender 时必须提供的元素名称。如果 toString 方法呈现传递给 Appender 的属性的值,则 Appender 应将 printObject 指定为“ true”。

Appender 还必须声明一个将创建追加器的 PluginFactory 方法。下面的示例显示了一个名为“ Stub”的 Appender,可以用作初始模板。

大多数 Appender 使用 Management 员。Management 器实际上“拥有”资源,例如 OutputStream 或套接字。重新配置发生时,将创建一个新的 Appender。但是,如果以前的 Manager 中的重要内容没有更改,则新的 Appender 将仅引用它,而不创建新的 Appender。这样可确保在进行重新配置时不会丢失事件,而在进行重新配置时无需暂停记录。

@Plugin(name = "Stub", category = "Core", elementType = "appender", printObject = true)
public final class StubAppender extends AbstractOutputStreamAppender<StubManager> {

    private StubAppender(String name,
                         Layout<? extends Serializable> layout,
                         Filter filter,
                         boolean ignoreExceptions,
                         StubManager  manager) {
        super(name, layout, filter, ignoreExceptions, true, manager);
    }

    @PluginFactory
    public static StubAppender createAppender(@PluginAttribute("name") String name,
                                              @PluginAttribute("ignoreExceptions") boolean ignoreExceptions,
                                              @PluginElement("Layout") Layout layout,
                                              @PluginElement("Filters") Filter filter) {

        if (name == null) {
            LOGGER.error("No name provided for StubAppender");
            return null;
        }

        StubManager manager = StubManager.getStubManager(name);
        if (manager == null) {
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new StubAppender(name, layout, filter, ignoreExceptions, manager);
    }
}

Layouts

布局将事件格式化为 Appenders 写到某些目标的可打印文本。所有布局都必须实现 Layout 接口。将事件格式化为 String 的布局应扩展 AbstractStringLayout,它将负责将 String 转换为所需的字节数组。

每个布局都必须使用 PluginComments 将自己声明为插件。类型必须为“ Core”,元素类型必须为“ layout”。如果插件的 toString 方法将提供对象及其参数的表示形式,则应该将 printObject 设置为 true。插件的名称必须与用户用来在其 Appender 配置中将其指定为元素的值匹配。插件还必须提供一个 Comments 为 PluginFactory 的静态方法,并在适当时为每个方法参数加上 PluginAttr 或 PluginElementComments。

@Plugin(name = "SampleLayout", category = "Core", elementType = "layout", printObject = true)
public class SampleLayout extends AbstractStringLayout {

    protected SampleLayout(boolean locationInfo, boolean properties, boolean complete,
                           Charset charset) {
    }

    @PluginFactory
    public static SampleLayout createLayout(@PluginAttribute("locationInfo") boolean locationInfo,
                                            @PluginAttribute("properties") boolean properties,
                                            @PluginAttribute("complete") boolean complete,
                                            @PluginAttribute(value = "charset", defaultStringValue = "UTF-8") Charset charset) {
        return new SampleLayout(locationInfo, properties, complete, charset);
    }
}

PatternConverters

PatternLayout 使用 PatternConverters 将日志事件格式化为可打印的 String。每个转换器负责一种操作,但是转换器可以自由地以复杂的方式格式化事件。例如,有几个转换器以各种方式操纵 Throwable 并将其格式化。

PatternConverter 必须首先使用标准的 Plugin 注解将其自身声明为 Plugin,但必须在 type 属性上指定“ Converter”的值。此外,转换器还必须指定 ConverterKeys 属性,以定义可以在模式中指定的令牌(以“%”字符开头)以标识转换器。

与大多数其他插件不同,转换器不使用 PluginFactory。相反,每个转换器都需要提供一个静态的 newInstance 方法,该方法接受 Strings 数组作为唯一参数。 String 数组是可以在转换键后面的花括号内指定的值。

下面显示了 Converter 插件的框架。

@Plugin(name = "query", category = "Converter")
@ConverterKeys({"q", "query"})
public final class QueryConverter extends LogEventPatternConverter {

    public QueryConverter(String[] options) {
    }

    public static QueryConverter newInstance(final String[] options) {
      return new QueryConverter(options);
    }
}

Plugin Builders

一些插件带有很多可选的配置选项。当插件有许多选项时,使用构建器类而不是工厂方法更易于维护(请参阅第 2 项:Joshua Bloch 在“有效 Java”中面对许多构造器参数时,请考虑使用构建器)。与带 Comments 的工厂方法相比,使用带 Comments 的构建器类还有其他优点:

  • 如果属性名称与字段名称匹配,则无需指定。

  • 可以在代码中指定默认值,而不是通过 Comments 指定(还允许在 Comments 中不允许使用运行时计算的默认值)。

  • 添加新的可选参数不需要重构现有的程序配置。

  • 使用构建器而不是带有可选参数的工厂方法更容易编写单元测试。

  • 默认值是通过代码指定的,而不是依赖于反射和注入的,因此它们可以以编程方式以及在配置文件中工作。

这是 ListAppender 的插件工厂的示例:

@PluginFactory
public static ListAppender createAppender(
        @PluginAttribute("name") @Required(message = "No name provided for ListAppender") final String name,
        @PluginAttribute("entryPerNewLine") final boolean newLine,
        @PluginAttribute("raw") final boolean raw,
        @PluginElement("Layout") final Layout<? extends Serializable> layout,
        @PluginElement("Filter") final Filter filter) {
    return new ListAppender(name, filter, layout, newLine, raw);
}

这是使用构建器模式的同一工厂:

@PluginBuilderFactory
public static Builder newBuilder() {
    return new Builder();
}

public static class Builder implements org.apache.logging.log4j.core.util.Builder<ListAppender> {

    @PluginBuilderAttribute
    @Required(message = "No name provided for ListAppender")
    private String name;

    @PluginBuilderAttribute
    private boolean entryPerNewLine;

    @PluginBuilderAttribute
    private boolean raw;

    @PluginElement("Layout")
    private Layout<? extends Serializable> layout;

    @PluginElement("Filter")
    private Filter filter;

    public Builder setName(final String name) {
        this.name = name;
        return this;
    }

    public Builder setEntryPerNewLine(final boolean entryPerNewLine) {
        this.entryPerNewLine = entryPerNewLine;
        return this;
    }

    public Builder setRaw(final boolean raw) {
        this.raw = raw;
        return this;
    }

    public Builder setLayout(final Layout<? extends Serializable> layout) {
        this.layout = layout;
        return this;
    }

    public Builder setFilter(final Filter filter) {
        this.filter = filter;
        return this;
    }

    @Override
    public ListAppender build() {
        return new ListAppender(name, filter, layout, entryPerNewLine, raw);
    }
}

Comments 中的唯一区别是使用@PluginBuilderAttribute 而不是@PluginAttribute,因此可以使用默认值和反射,而不是在 Comments 中指定它们。两种 Comments 都可以在构建器中使用,但是前者更适合于字段注入,而后者更适合于参数注入。否则,字段上均支持相同的注解(@ PluginConfiguration,@ PluginElement,@ PluginNode 和@PluginValue)。请注意,仍然需要工厂方法来提供构建器,并且该工厂方法应使用@PluginBuilderFactory 进行 Comments。

在解析配置后构建插件时,将使用插件构建器(如果可用),否则将使用插件工厂方法作为后备。如果一个插件都不包含工厂,则不能从配置文件中使用它(当然仍然可以通过编程方式使用它)。

这是以编程方式使用插件工厂与插件构建器的示例:

ListAppender list1 = ListAppender.createAppender("List1", true, false, null, null);
ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLine(true).build();

Custom ContextDataProvider

ContextDataProvider(在 Log4j 2.13.2 中引入)是一个接口应用程序,并且库可用于将其他键值对注入 LogEvent 的上下文数据中。 Log4j 的 ThreadContextDataInjector 使用 java.util.ServiceLoader 定位和加载 ContextDataProvider 实例。 Log4j 本身使用 org.apache.logging.log4j.core.impl.ThreadContextDataProvider 将 ThreadContextData 添加到 LogEvent 中。定制实现应实现 org.apache.logging.log4j.core.util.ContextDataProvider 接口,并通过在名为 META-INF/services/org.apache.logging.log4j.core 的文件中定义实施类,将其声明为服务。 util.ContextDataProvider。

自定义 ThreadContextMap 实现

通过将系统属性 log4j2.garbagefreeThreadContextMap 设置为 true,可以安装基于 StringMap 的无垃圾上下文 Map。 (Log4j 必须为enabled才能使用 ThreadLocals。)

通过将系统属性 log4j2.threadContextMap 设置为实现 ThreadContextMap 接口的类的标准类名,可以安装任何自定义 ThreadContextMap 实现。通过还实现 ReadOnlyThreadContextMap 接口,应用程序可以通过ThreadContext::getThreadContextMap方法访问您自定义的 ThreadContextMap 实现。

Custom_Plugins

请参阅手册的Plugins部分。