log4j2之插件

背景:log4j版本2.11.0,且开启了异步日志

log4j2上下文加载

  1. Log4jContextFactory上下文工厂创建日志上下文
  2. AsyncLoggerContextSelector异步日志上下文选择器获取上下文。locateContext定位上下文,根据classloader对应的key(AsyncContext@+hashcode(10进制))从上下文Map中获取上下文;递归parentClassLoader,如果不存在则创建日志上下文AsyncLoggerContext
  3. 如果存在外部上下文则设置setExternalContext
  4. 如果上下文状态为已初始化但还未启动即_INITIALIZED。则启动上下文_
  5. 启动loggerDisruptor。回调父类(LoggerContext)start方法
  6. 如果当前状态为_INITIALIZED,则更新状态为启动中STARTING并再配置上下文_reconfigure
  7. 根据log4j.configurationFactory配置获取配置工厂,如果不为空将对应的工厂类添加至待处理列表

插件管理器加载配置

  1. 创建ConfigurationFactory类插件管理器PluginManager获取配置工厂ConfigurationFactory。插件管理器收集插件collectPlugins
  2. 插件注册器PluginRegistry加载内建插件集合loadFromMainClassLoader
  3. 缓存中获取插件集合,存在直接返回
  4. 根据Loader的类加载器反序列化Cache文件
  5. 创建PluginCache插件缓存实例
  6. 读取数据文件:META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
  7. 插件缓存加载数据文件loadCacheFiles,将数据反序列化为PluginEntry类型缓存至插件缓存PluginCache.categories:configurationfactory,core,converter,lookup,fileconverter,typeconverter
  8. 遍历categories列表,根据PluginEntry配置获取插件类型封装为PluginType类型,返回newPluginsByCategory集合缓存至pluginsByCategoryRef
  9. 如果内建插件集合为空,则根据org.apache.logging.log4j.core包路径加载作为内建插件集合,扫描包路径下Plugin注解的类。或者PluginAliases注解的类(代表一个_Plugin、PluginAttribute或者PluginBuilderAttribute_集合),如果插件注解为空Plugin._EMPTY则使用_alias重命名否则使用elementType作为aliasElementName
  10. 根据categoryLowerCase小写名称从内建插件中合并至newPlugins:mergeByName
  11. 上面是从classLoader中加载插件,如果存在_OSGi Bundles绑定的插件同样加载并合并mergeByName_
  12. 根据插件管理器_PACKAGES配置同样加载并合并_loadFromPackage
  13. 如果入参packages不为空则同样加载并合并
  14. 将新插件newPlugins绑定至插件管理器的plugins属性
  15. 根据插件管理器获取ConfigurationFactory对应的插件集合并根据Order注解的顺序排序(按照order由大到小排序)
  16. 反射调用ConfigurationFactory的无参构造器实例化工厂并添加至_factories集合_
  17. 返回_configFactory实例默认工厂实现_Factory

配置工厂获取配置

  1. 如果存在外部上下文并且存在configLocation配置,则根据配置文件ConfigurationSource获取配置例如:XmlConfigurationFactory
  2. 不存在externalContext外部上下文classloader则CL为null回调默认工厂Factory获取配置getConfiguration
  3. 如果configLocation为空则读取log4j.configurationFile配置,如果配置不为空则根据配置文件指定的sourceLocation获取配置。
  4. 如果为多个配置则封装为CompositeConfiguration类型,否则返回单个配置
  5. 如果configLocation依然为空,则遍历从插件管理中获取的ConfigurationFactory类型的_factories_集合
  6. 如果配置工厂支持_ALL_TYPES _= "*"类型,则回调工厂getConfiguration方法获取Configuration配置,如果配置不为空则返回,后面的配置工厂则不会再继续调用,例如截图中只会调用自动以的配置log4j2之插件

日志上下文根据自定义配置配置

  1. 根据返回的Configuration配置实例配置日志上下文LoggerContext.setConfiguration
  2. 如果配置文件为空则返回默认配置什么都不做。默认配置DefaultConfiguration级别为ERROR的控制台标准输出管道
  3. 获取配置文件ContextProperties组件属性getComponent,设置本地hostname以及上下文名称log4j2之插件
  4. 启动Configuration配置start
  5. 将配置绑定至当前LoggerContext日志上下文
  6. 发送配置变更事件firePropertyChangeEvent:PropertyChangeEvent
  7. 回调事件监听器PropertyChangeListener.propertyChange方法

至此日志上下文加载完成

Logger与Filter过滤

@Override
protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
    return new AsyncLogger(ctx, name, messageFactory, loggerDisruptor);
}
  1. 根据日志上下文创建Logger
  2. 调用父类构造器
protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
    super(name, messageFactory);
    this.context = context;
    privateConfig = new PrivateConfig(context.getConfiguration(), this);
}
  1. 根据日志上下文获取配置文件以及当前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;
}
  1. 根据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);
}
  1. 例如我们之前谈到的自定义配置CustomXmlConfigurationFactory
  2. 常见过滤配置还有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

  1. 创建LoggerContext弱引用、rootNode、插件管理器(core目录)、ContextProperties组件map,设置状态为初始化中
  2. 解析配置xml的dom树
  3. 创建StatusConfiguration配置绑定dom配置的各个属性例如:logger
  4. 启动配置start,LoggerContext在获取配置后会启动配置
  5. 初始化配置initialize
  6. 创建ScriptManager
  7. 获取core插件
  8. 获取level插件,初始化level插件,使用插件class的classloader加载插件的class类
  9. 配置setup:将dom树的rootElement元素所有子元素与rootNode关联
  10. doConfigure配置,创建配置createConfiguration,如果是集合、列表类型创建对应的类型绑定至Node.object属性;否则返回使用PluginBuilder构建者构建对应的插件实例绑定至Node.object属性
  11. 如果子节点为Appenders则绑定至appenders
  12. 如果子节点是Filter类型则添加至配置,如果是过滤链则添加至过滤链
  13. 如果子节点为Loggers则获取对应的配置绑定至loggerConfigs
  14. 启动loggerConfigs
  15. 启动appenders
  16. 启动当前配置绑定的filter过滤

总结

log4j插件通过插件管理器PluginManager扫描读取插件配置管理所有支持的插件class配置。通过PluginBuilder对相应的插件进行属性注入以及实例化。相对于jdk官方的spi的优点有很多,比如支持k-v的方式注入。根据插件类型注入。但是缺点则是实现有些复杂,并且配置文件可读性差,使用也较为复杂

上一篇:FreeMarker 页面静态化—快速入门(三)


下一篇:springboot 集成freemarker