拓展 Log4j
Log4j 2提供了许多操作和扩展的方法。本节概述了Log4j 2实现直接支持的各种方式。
LoggerContextFactory
LoggerContextFactory将Log4j API绑定到它的实现。
Log4j LogManager通过使用java.util.ServiceLoader来定位org.apache.logging.log4j.spi.Provider的所有实例来定位LoggerContextFactory。
每个实现都必须提供一个类来扩展apache.logging.log4j.spi。提供者和应该有一个无参数的构造函数,该构造函数将优先级、与之兼容的API版本以及实现org.apache.logging.log4j.spi.LoggerContextFactory的类委托给提供者的构造函数。
Log4j将比较当前的API版本,如果它是兼容的,实现将被添加到提供者列表中。
org.apache.logging.log4j.log中的API版本。只有在API中添加了实现需要知道的特性时,才会更改LogManager。
如果找到了多个有效实现,则使用Priority的值来标识具有最高优先级的工厂。
最后,实现org.apache.logging.log4j.spi.LoggerContextFactory 的类将被实例化并绑定到LogManager。
在Log4j 2中,这由Log4jContextFactory提供。
1) 应用程序可以更改LoggerContextFactory,它将被创建到日志记录实现的绑定。
实现一个新的LoggerContextFactory。
实现一个扩展org.apache.logging.spi.Provider的类。使用一个无参数的构造函数调用超类的构造函数,该构造函数使用Priority、API版本、LoggerContextFactory类以及可选的ThreadContextMap实现类。
创建一个META-INF/services/org.apache.logging.spi。提供程序文件,该文件包含实现org.apache.logging.spi.Provider的类的名称。
2) 设置系统属性log4j2。loggerContextFactory到要使用的loggerContextFactory类的名称。
3) 在属性文件 log4j2.LogManager.properties
设置属性 log4j.loggerContextFactory
,用来确定 LoggerContextFactory 类的名称。属性文件必须在类路径上。
ContextSelector
上下文选择器由Log4j LoggerContext工厂调用。
它们执行定位或创建LoggerContext的实际工作,LoggerContext是logger及其配置的锚。
ContextSelectors可以自由地实现他们想要管理LoggerContexts的任何机制。默认的Log4jContextFactory检查是否存在一个名为“Log4jContextSelector”的系统属性。
如果找到,该属性应该包含实现要使用的ContextSelector的类的名称。
Log4j提供了五个contextselector:
1) BasicContextSelector
使用一个存储在ThreadLocal中的LoggerContext或者一个普通的LoggerContext。
2) ClassLoaderContextSelector
将LoggerContexts与创建getLogger调用调用者的ClassLoader关联起来。这是默认的ContextSelector。
3) JndiContextSelector
通过查询JNDI来定位LoggerContext。从Log4j 2.17.0开始,JNDI操作需要log4j2。
enableJndiContextSelector=true设置为系统属性或相应的环境变量,以使此查找发挥作用。
参见enableJndiContextSelector系统属性。
4) AsyncLoggerContextSelector
创建一个LoggerContext,确保所有的记录器都是AsyncLoggers。
5) BundleContextSelector
将LoggerContexts与创建getLogger调用方的bundle的ClassLoader关联起来。这在OSGi环境中是默认启用的。
ConfigurationFactory
修改日志的配置方式通常是人们最感兴趣的领域之一。
这样做的主要方法是通过实现或扩展ConfigurationFactory。Log4j提供了两种添加新configurationfactory的方法。
第一种方法是定义名为“log4j”的系统属性。configurationFactory”到应该首先搜索配置的类的名称。第二种方法是将ConfigurationFactory定义为一个插件。
然后按顺序处理所有configurationfactory。调用每个工厂的getSupportedTypes方法来确定它支持的文件扩展名。如果配置文件位于指定的文件扩展名之一中,则将控制传递给该ConfigurationFactory,以加载配置并创建configuration对象。
大多数配置都扩展了BaseConfiguration类。该类期望子类将处理配置文件并创建Node对象的层次结构。
每个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对象是应用程序创建的记录器绑定到配置的地方。
Log4j实现要求所有LoggerConfigs都基于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配置为所有日志记录器都是异步时,日志事件将在一个循环缓冲区中预分配,并且不会使用LogEventFactory。
MessageFactory
MessageFactory用于生成Message对象。应用程序可以通过设置系统属性log4j2的值来替换标准的ParameterizedMessageFactory(或无垃圾模式下的ReusableMessageFactory)。
messageFactory转换为自定义messageFactory类的名称。
Logger.entry()和Logger.exit()方法的流消息有一个单独的FlowMessageFactory。
应用程序可以通过设置系统属性log4j2的值来替换DefaultFlowMessageFactory。将flowMessageFactory更改为自定义flowMessageFactory类的名称。
查找 LookUps
查找是执行参数替换的方法。在配置初始化期间,创建了一个“Interpolator”,用于定位所有查找并注册它们,以便在需要解析变量时使用。插值器将变量名的“前缀”部分匹配到已注册的Lookup,并将控制权传递给它以解析变量。
Lookup必须使用类型为“Lookup”的Plugin注释来声明。Plugin注释中指定的名称将用于匹配前缀。不像其他插件,查找不使用PluginFactory。
相反,它们需要提供一个不接受参数的构造函数。下面的示例显示了一个Lookup,它将返回System Property的值。
所提供的查找记录如下
@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来声明的。
Plugin注释上的name属性用于指定用户应该用来启用Filter的元素的名称。
将printObject属性的值指定为“true”,表示对toString的调用将在处理配置时格式化过滤器的参数。
过滤器还必须指定一个PluginFactory方法,该方法将被调用来创建过滤器。
下面的示例显示了一个Filter,用于根据日志级别拒绝LogEvents。
注意典型的模式,其中所有筛选方法都解析为单个筛选方法。
@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传递一个事件,(通常)调用Layout来格式化事件,然后以所需的任何方式“发布”事件。
Appenders被声明为插件,类型为Core,元素类型为appender。
Plugin注释上的name属性指定了用户必须在其配置中提供的元素的名称,以便使用Appender。如果toString方法呈现传递给Appender的属性值,Appender应该将printObject指定为”true”。
appender还必须声明一个PluginFactory方法来创建appender。
下面的例子展示了一个名为“Stub”的Appender,它可以用作初始模板。
大多数appender使用manager。管理器实际上“拥有”资源,例如OutputStream或套接字。当重新配置发生时,将创建一个新的Appender。
但是,如果之前的Manager中没有任何重要的更改,则新的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);
}
}
布局
布局将事件格式化为Appenders写入某个目的地的可打印文本。所有布局都必须实现布局接口。
将事件格式化为String的布局应该扩展AbstractStringLayout,后者负责将String转换为所需的字节数组。
每个布局都必须使用plugin注释将自己声明为插件。类型必须为“Core”,elementType必须为“layout”。
如果插件的toString方法将提供对象及其参数的表示,printObject应该设置为true。
插件的名称必须与用户用来在Appender配置中指定它作为元素的值匹配。
插件还必须提供一个静态方法,标注为PluginFactory,并且每个方法的参数都适当标注为PluginAttr或PluginElement。
@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。
每个转换器负责一种操作,但是转换器可以自由地以复杂的方式格式化事件。
例如,有几个转换器可以操作Throwables并以各种方式格式化它们。
PatternConverter必须首先使用标准Plugin注释将自己声明为Plugin,但必须在type属性上指定“Converter”的值。
此外,Converter还必须指定ConverterKeys属性来定义可以在模式中指定的令牌(前面有’%’字符),以标识Converter。
与大多数其他插件不同,转换器不使用PluginFactory。相反,每个Converter都需要提供一个静态newInstance方法,该方法接受string数组作为唯一的参数。
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在Effective Java中面对许多构造器参数时考虑使用构造器)。
与带注释的工厂方法相比,使用带注释的构建器类还有其他一些优点:
-
如果属性名与字段名匹配,则不需要指定属性名。
-
默认值可以在代码中指定,而不是通过注释(也允许运行时计算的默认值,这在注释中是不允许的)。
-
添加新的可选参数不需要重构现有的可编程配置。
-
使用构建器编写单元测试比使用带有可选参数的工厂方法更容易。
-
默认值是通过代码指定的,而不是依赖于反射和注入,因此它们以编程方式工作,就像在配置文件中一样。
下面是一个来自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);
}
}
注释的唯一区别是使用 @PluginBuilderAttribute
而不是 @PluginAttribute
,这样就可以使用默认值和反射,而不是在注释中指定它们。
这两种注释都可以在构建器中使用,但是前者更适合字段注入,而后者更适合参数注入。
否则,相同的注释(@PluginConfiguration
, @PluginElement
, @PluginNode
和 @PluginValue
)都支持字段。
请注意,工厂方法仍然需要提供构建器,并且该工厂方法应该使用@PluginBuilderFactory注释。
当配置解析后构建插件时,如果可用,将使用插件构建器,否则将使用插件工厂方法作为备用方法。
如果一个插件既不包含工厂,那么它就不能从配置文件中使用(当然它仍然可以通过编程方式使用)。
下面是一个使用插件工厂和插件构建器编程的例子:
ListAppender list1 = ListAppender.createAppender("List1", true, false, null, null);
ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLine(true).build();
自定义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
。
通过在名为 META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider
的文件中定义实现类,并将其声明为服务。
自定义ThreadContextMap实现
可以通过设置系统属性log4j2来安装一个基于stringmap的无垃圾的上下文映射。garbagefreeThreadContextMap为true。(使用ThreadLocals必须启用Log4j。)
任何自定义的ThreadContextMap实现都可以通过设置系统属性log4j2来安装。将threadContextMap映射为实现threadContextMap接口的类的完全限定类名。
通过实现ReadOnlyThreadContextMap接口,应用程序可以通过ThreadContext::getThreadContextMap方法访问自定义的ThreadContextMap实现。
Custom_Plugins
请参阅手册的插件部分。
参考资料
https://logging.apache.org/log4j/2.x/manual/extending.html