log4j2上下文加载
- Log4jContextFactory上下文工厂创建日志上下文
- AsyncLoggerContextSelector异步日志上下文选择器获取上下文。locateContext定位上下文,根据classloader对应的key(AsyncContext@+hashcode(10进制))从上下文Map中获取上下文;递归parentClassLoader,如果不存在则创建日志上下文AsyncLoggerContext
- 如果存在外部上下文则设置setExternalContext
- 如果上下文状态为已初始化但还未启动即_INITIALIZED。则启动上下文_
- 启动loggerDisruptor。回调父类(LoggerContext)start方法
- 如果当前状态为_INITIALIZED,则更新状态为启动中STARTING并再配置上下文_reconfigure
- 根据log4j.configurationFactory配置获取配置工厂,如果不为空将对应的工厂类添加至待处理列表
插件管理器加载配置
- 创建ConfigurationFactory类插件管理器PluginManager获取配置工厂ConfigurationFactory。插件管理器收集插件collectPlugins
- 插件注册器PluginRegistry加载内建插件集合loadFromMainClassLoader
- 缓存中获取插件集合,存在直接返回
- 根据Loader的类加载器反序列化Cache文件
- 创建PluginCache插件缓存实例
- 读取数据文件:META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
- 插件缓存加载数据文件loadCacheFiles,将数据反序列化为PluginEntry类型缓存至插件缓存PluginCache.categories:configurationfactory,core,converter,lookup,fileconverter,typeconverter
- 遍历categories列表,根据PluginEntry配置获取插件类型封装为PluginType类型,返回newPluginsByCategory集合缓存至pluginsByCategoryRef
- 如果内建插件集合为空,则根据org.apache.logging.log4j.core包路径加载作为内建插件集合,扫描包路径下Plugin注解的类。或者PluginAliases注解的类(代表一个_Plugin、PluginAttribute或者PluginBuilderAttribute_集合),如果插件注解为空Plugin._EMPTY则使用_alias重命名否则使用elementType作为aliasElementName
- 根据categoryLowerCase小写名称从内建插件中合并至newPlugins:mergeByName
- 上面是从classLoader中加载插件,如果存在_OSGi Bundles绑定的插件同样加载并合并mergeByName_
- 根据插件管理器_PACKAGES配置同样加载并合并_loadFromPackage
- 如果入参packages不为空则同样加载并合并
- 将新插件newPlugins绑定至插件管理器的plugins属性
- 根据插件管理器获取ConfigurationFactory对应的插件集合并根据Order注解的顺序排序(按照order由大到小排序)
- 反射调用ConfigurationFactory的无参构造器实例化工厂并添加至_factories集合_
- 返回_configFactory实例默认工厂实现_Factory
配置工厂获取配置
- 如果存在外部上下文并且存在configLocation配置,则根据配置文件ConfigurationSource获取配置例如:XmlConfigurationFactory
- 不存在externalContext外部上下文classloader则CL为null回调默认工厂Factory获取配置getConfiguration
- 如果configLocation为空则读取log4j.configurationFile配置,如果配置不为空则根据配置文件指定的sourceLocation获取配置。
- 如果为多个配置则封装为CompositeConfiguration类型,否则返回单个配置
- 如果configLocation依然为空,则遍历从插件管理中获取的ConfigurationFactory类型的_factories_集合
- 如果配置工厂支持_ALL_TYPES _= "*"类型,则回调工厂getConfiguration方法获取Configuration配置,如果配置不为空则返回,后面的配置工厂则不会再继续调用,例如截图中只会调用自动以的配置
日志上下文根据自定义配置配置
- 根据返回的Configuration配置实例配置日志上下文LoggerContext.setConfiguration
- 如果配置文件为空则返回默认配置什么都不做。默认配置DefaultConfiguration级别为ERROR的控制台标准输出管道
- 获取配置文件ContextProperties组件属性getComponent,设置本地hostname以及上下文名称
- 启动Configuration配置start
- 将配置绑定至当前LoggerContext日志上下文
- 发送配置变更事件firePropertyChangeEvent:PropertyChangeEvent
- 回调事件监听器PropertyChangeListener.propertyChange方法
Logger与Filter过滤
@Override
protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
return new AsyncLogger(ctx, name, messageFactory, loggerDisruptor);
}
- 根据日志上下文创建Logger
- 调用父类构造器
protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
super(name, messageFactory);
this.context = context;
privateConfig = new PrivateConfig(context.getConfiguration(), this);
}
- 根据日志上下文获取配置文件以及当前logger实例创建日志配置PrivateConfig
public PrivateConfig(final Configuration config, final Logger logger) {
this.config = config;
this.loggerConfig = config.getLoggerConfig(getName());
this.loggerConfigLevel = this.loggerConfig.getLevel();
this.intLevel = this.loggerConfigLevel.intLevel();
this.logger = logger;
}
- 根据Logger的name从Configuration获取对应的日志配置LoggerConfig
public LoggerConfig getLoggerConfig(final String loggerName) {
LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
if (loggerConfig != null) {
return loggerConfig;
}
String substr = loggerName;
//按照"."符号一层层向上查找匹配的日志配置,如果不存在则返回默认root配置
while ((substr = NameUtil.getSubName(substr)) != null) {
loggerConfig = loggerConfigs.get(substr);
if (loggerConfig != null) {
return loggerConfig;
}
}
return root;
}
//默认DefaultConfiguration配置对应的默认LoggerConfig配置
protected void setToDefault() {
// LOG4J2-1176 facilitate memory leak investigation
setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
.withPattern(DefaultConfiguration.DEFAULT_PATTERN)
.withConfiguration(this)
.build();
final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
appender.start();
addAppender(appender);
final LoggerConfig rootLoggerConfig = getRootLogger();
//配置Appender、level(为空则代表记录所有事件)、filter(为空代表不存在过滤器)
rootLoggerConfig.addAppender(appender, null, null);
final Level defaultLevel = Level.ERROR;
final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL,
defaultLevel.name());
final Level level = Level.valueOf(levelName);
rootLoggerConfig.setLevel(level != null ? level : defaultLevel);
}
- 例如我们之前谈到的自定义配置CustomXmlConfigurationFactory
- 常见过滤配置还有ThresholdFilter、LevelRangeFilter通过插件管理注入
@Plugin(
name = "CustomXmlConfigurationFactory",
category = "ConfigurationFactory"
)
@Order(10)
public class CustomXmlConfigurationFactory extends ConfigurationFactory {
private static final String[] SUFFIXES = new String[]{".xml", "*"};
public CustomXmlConfigurationFactory() {
}
public String[] getSupportedTypes() {
return SUFFIXES;
}
public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
XmlConfiguration template = new XmlConfiguration(loggerContext, ConfigurationSource.fromResource("log4j2-template.xml", this.getClass().getClassLoader()));
XmlConfiguration specific = new XmlConfiguration(loggerContext, source);
return new CompositeConfiguration(Lists.newArrayList(new XmlConfiguration[]{template, specific}));
}
}
XmlConfiguration
- 创建LoggerContext弱引用、rootNode、插件管理器(core目录)、ContextProperties组件map,设置状态为初始化中
- 解析配置xml的dom树
- 创建StatusConfiguration配置绑定dom配置的各个属性例如:logger
- 启动配置start,LoggerContext在获取配置后会启动配置
- 初始化配置initialize
- 创建ScriptManager
- 获取core插件
- 获取level插件,初始化level插件,使用插件class的classloader加载插件的class类
- 配置setup:将dom树的rootElement元素所有子元素与rootNode关联
- doConfigure配置,创建配置createConfiguration,如果是集合、列表类型创建对应的类型绑定至Node.object属性;否则返回使用PluginBuilder构建者构建对应的插件实例绑定至Node.object属性
- 如果子节点为Appenders则绑定至appenders
- 如果子节点是Filter类型则添加至配置,如果是过滤链则添加至过滤链
- 如果子节点为Loggers则获取对应的配置绑定至loggerConfigs
- 启动loggerConfigs
- 启动appenders
- 启动当前配置绑定的filter过滤
总结
log4j插件通过插件管理器PluginManager扫描读取插件配置管理所有支持的插件class配置。通过PluginBuilder对相应的插件进行属性注入以及实例化。相对于jdk官方的spi的优点有很多,比如支持k-v的方式注入。根据插件类型注入。但是缺点则是实现有些复杂,并且配置文件可读性差,使用也较为复杂