分享

探索Java日志的奥秘: 底层日志系统

 苏心阁 2019-04-10

log4j2是apache在log4j的基础上,参考logback架构实现的一套新的日志系统(我感觉是apache害怕logback了)。

log4j2的官方文档上写着一些它的优点:

在拥有全部logback特性的情况下,还修复了一些隐藏问题

API 分离:现在log4j2也是门面模式使用日志,默认的日志实现是log4j2,当然你也可以用logback(应该没有人会这么做)

性能提升:log4j2包含下一代基于LMAX Disruptor library的异步logger,在多线程场景下,拥有18倍于log4j和logback的性能

多API支持:log4j2提供Log4j 1.2, SLF4J, Commons Logging and java.util.logging (JUL) 的API支持

避免锁定:使用Log4j2 API的应用程序始终可以选择使用任何符合SLF4J的库作为log4j-to-slf4j适配器的记录器实现

自动重新加载配置:与Logback一样,Log4j 2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。

高级过滤: 与Logback一样,Log4j 2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。

插件架构: Log4j使用插件模式配置组件。因此,您无需编写代码来创建和配置Appender,Layout,Pattern Converter等。Log4j自动识别插件并在配置引用它们时使用它们。

属性支持:您可以在配置中引用属性,Log4j将直接替换它们,或者Log4j将它们传递给将动态解析它们的底层组件。

Java 8 Lambda支持

自定义日志级别

产生垃圾少:在稳态日志记录期间,Log4j 2 在独立应用程序中是无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应时间性能。

和应用server集成:版本2.10.0引入了一个模块log4j-appserver,以改进与Apache Tomcat和Eclipse Jetty的集成。

在这段逻辑中,LogManager优先通过配置文件”log4j2.component.properties”通过配置项”log4j2.loggerContextFactory”来获取LoggerContextFactory,如果用户做了对应的配置,通过newCheckedInstanceOf方法实例化LoggerContextFactory的对象,最终的实现方式为:

再看构造方法:

原来这里是为了让osgi可以阻止启动。

再回到logManager:

可以看到在加载完Provider之后,会做factory的绑定:

这里有一个getContext方法,跟进,

直接看start:

可以看到每一个configuration都是从ConfigurationFactory拿出来的,我们先看看这个类的getInstance看看:

这里可以看到ConfigurationFactory中利用了PluginManager来进行初始化,PluginManager会将ConfigurationFactory的子类加载进来,默认使用的XmlConfigurationFactory,JsonConfigurationFactory,YamlConfigurationFactory这三个子类,这里插件化加载暂时按下不表。

回到reconfigure这个方法,我们看到获取ConfigurationFactory实例之后会去调用getConfiguration方法:

这个方法最重要的步骤就是config.start,这才是真正做配置解析的

发现这里面有一个比较重要的方法constructHierarchy,跟进:

发现就是对刚刚获取的configuration进行解析,然后塞进正确的地方。回到start方法,可以看到昨晚配置之后就是开启logger和appender了。

异步

AsyncAppender

一路跟进

继续跟进:

这里实际打日志的方法居然是交给一个config去实现的。。。感觉有点奇怪。。跟进看看

接下来就是callAppender了,我们直接开始看AsyncAppender的append方法:

直接从AsyncLogger的logMessage看进去:

这里的逻辑很简单,就是将日志相关的信息转换成RingBufferLogEvent(RingBuffer是Disruptor的无所队列),然后将其发布到RingBuffer中。发布到RingBuffer中,那肯定也有消费逻辑。这时候有两种方式可以找到这个消费的逻辑。

找disruptor被使用的地方,然后查看,但是这样做会很容易迷惑

按照Log4j2的尿性,这种Logger都有对应的start方法,我们可以从start方法入手寻找

在start方法中,我们找到了一段代码:

final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler};

disruptor.handleEventsWith(handlers);

直接看看这个RingBufferLogEventHandler的实现:

顺着接口找上去,发现一个接口:

这个方法就是实际打日志了,AsyncLogger看起来还是比较简单的,只是使用了一个Disruptor。

插件化

之前在很多代码里面都可以看到

final PluginManager manager = new PluginManager(CATEGORY);

manager.collectPlugins(pluginPackages);

其实整个log4j2为了获得更好的扩展性,将自己的很多组件都做成了插件,然后在配置的时候去加载plugin。

跟进collectPlugins。

<div '="">

(不太重要的方法省略)

我们可以看到在process方法中,PluginProcessor会先收集所有的Plugin,然后在写入文件。这样做的好处就是可以省去反射时候的开销。

然后我又看了一下Plugin这个注解,发现它的RetentionPolicy是RUNTIME,一般来说PluginProcessor是搭配RetentionPolicy.SOURCE,CLASS使用的,而且既然你把自己的Plugin扫描之后写在文件中了,RetentionPolicy就没有必要是RUNTIME了吧,这个是一个很奇怪的地方。

小结

总算是把Log4j2的代码看完了,发现它的设计理念很值得借鉴,为了灵活性,所有的东西都设计成插件式。互联网技术日益发展,各种中间件层出不穷,而作为工程师的我们更需要做的是去思考代码与代码之间的关系,毫无疑问的是,解耦是最具有美感的关系。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约