一個稱得上優秀的框架,必備的要素之一可以通過某種約定的格式讀取到所運行環境中的配置信息。本文中我們就來感受下log4j2實現此項功能時的精妙設計。
一 概述
“ Lookups provide a way to add values to the Log4j configuration at arbitrary places. They are a particular type of Plugin that implements the StrLookup interface. ”
以上內容複製於log4j2的官方文檔lookup – Office Site。其清晰地說明了lookup的主要功能就是提供另外一種方式以添加某些特殊的值到日誌中,以最大化鬆散耦合地提供可配置屬性供使用者以約定的格式進行調用。
二. 配置示例
以下列舉了兩個主要使用的位置;當然不僅僅如此,log4j2允許你在任何需要的地方使用約定格式來獲取環境中的指定配置信息。
<properties>
<!-- 之後我們就可以以 ${logPath}來引用該屬性值 -->
<property name="logPath">${sys:catalina.home}/xmlogs</property>
</properties>
<!– 這裏的${hostName} 是由log4j2默認提供的, 其值爲程序所在的服務器的主機名 –>
<!– 至於${thread:threadName}, 將是本次我們所提供一個自定義lookup示例 –>
<PatternLayout pattern=“[${hostName}];[${thread:threadName}];[%X{user}];[$${ctx:user}];[$${date:YYYY-MM/dd}]” />
关于log4j2的详细使用说明,请参看官网开发文档。
三. 分析
我们分析一下lookup机制,都会在什么地方级别的日志中出现。首先我们要了解一点日志等级,在log4j2中, 共有8个级别,按照从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF。
-
All:最低等级的,用于打开所有日志记录. -
Trace:是追踪,就是程序推进一下. -
Debug:指出细粒度信息事件对调试应用程序是非常有帮助的. -
Info:消息在粗粒度级别上突出强调应用程序的运行过程. -
Warn:输出警告及warn以下级别的日志. -
Error:输出错误信息日志. -
Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志. -
All:最低等级的,用于打开所有日志记录. -
Trace:是追踪,就是程序推进一下. -
Debug:指出细粒度信息事件对调试应用程序是非常有帮助的. -
Info:消息在粗粒度级别上突出强调应用程序的运行过程. -
Warn:输出警告及warn以下级别的日志. -
Error:输出错误信息日志. -
Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.
程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少 。
详细代码可以看这里
也就是说,在不管什么级别的日志下都可以出发lookup。但是为什么有些级别的日志下却不可以触发呢?那是因为你的日志级别设置的太高,导致log4j根本就没打印日志内容。
在org.apache.logging.log4j.core.pattern.MessagePatternConverter#format
中,会按字符检测每条日志,一旦发现某条日志中包含$ {
,则触发替换机制,也就是将表达式内的内容替换成真实的内容,其中config.getStrSubstitutor().replace(event, value)
执行下一步替换操作,关键代码如图
org.apache.logging.log4j.core.lookup.StrSubstitutor#substitute(org.apache.logging.log4j.core.LogEvent, java.lang.StringBuilder, int, int, java.util.List<java.lang.String>)
中,其实就是一个简单的字符串提取,然后找到lookup的内容并替换。函数的文档如下
没啥说的,一个简单的字符串查找函数,学过数据结构的都会,不详细介绍了。
在函数的这个地方,执行变量解析,如图
在这个函数,执行查找,也就是根据变量的协议,关键代码+文档如图
剩下就是一个简单的字符串查找函数,从字符串中提取类似于url的结构去解析,关键代码如下
值得注意的是,log4j2支持很多协议,例如通过ldap查找变量,通过docker查找变量,详细参考这里https://www.docs4dev.com/docs/zh/log4j2/2.x/all/manual-lookups.html
代码结构如图
由以上類層次結構圖可以看出
-
log4j2提供不下十種獲取所運行環境配置信息的方式,基本能滿足實際運行環境中獲取各類配置信息的需求。 -
我們在自定義lookup時,可以根據自身需求自由選擇繼承自StrLookup,AbstractLookup,AbstractConfigurationAwareLookup等等來簡化我們的代碼。以上默認提供的各類lookup,其取值來源看官可以通過下面給出的引用鏈接中的第二個進行詳細的瞭解,我就不再在這裏贅述一遍了。
接下來我們來探索一些稍微深入的內容,以及一些細節性的內容。
-
作爲lookup對外門面的Interpolator是通過 log4j2中負責解析節點的PropertiesPlugin類來併入執行流程中的。具體源碼可以參見PropertiesPlugin.configureSubstitutor方法。其中注意的是,我們在中提供的屬性是以default的優先級提供給外界的。 -
作爲lookup對外門面的Interpolator,在其構造函數中載入了所有category值爲StrLookup.CATEGORY的plugin【即包括log4j2內置的(“org.apache.logging.log4j.core” package下的),也包括用戶自定義的(log4j2.xml文件中的 Configuration.packages 屬性值指示的package下的)】。 -
Interpolator可以單獨使用,但某些值可能取不到。 -
獲取MDC中的內容,log4j2提供了兩種方式:$${ctx:user}或%X{user}。
原文始发于微信公众号(宽字节安全):Log4j2 研究之lookup