Log4j2分析与实践-Layouts

Layouts

    Appender使用Layout来按需要格式化日志事件。在Log4j1和Logback中,Layout用来将事件转换为字符串。在Log4j2中,Layout返回一个字节数组,这就使得Layout的结果可以在多种类型的Appender中使用。然而,这就意味着你需要为Layout配置一个字符集来确保字节数组中包含正确的值。
    Layout的根类org.apache.logging.log4j.core.layout.AbstractStringLayout使用了一个Charset,默认为UTF-8.每个继承自AbstractStringLayout都可以为自己指定一个默认的字符集。
    Log4j 2.4.1添加了自定义字符编码ISO-8859-1和US-ASCII,对在Java8上编译的Log4j2在Java7上运行也有一定的性能提升。对于仅仅使用ISO-8859-1的应用,指定一个字符集会大大提升性能。

    Log4j提供了多种Layout供开发者使用,包含CSV、GELF、HTML、JSON、PATTERN、RFC-5424、Serialized、Syslog、XML、YAML和Location Infomation。本人仅介绍常见的PatternLayout。


PatternLayout

    可以使用pattern字符串进行灵活地配置。这个类的目的就是对LogEvent进行格式化并返回结果,结果的格式取决于转换pattern。
转换pattern跟C语言中的printf类似,一种转换模式由纯文本和格式控制表达式构成。
    需要注意的是,模式表达式中也有可能包含纯文本和特殊字符,特殊字符包括\t、\n、\r、\f,使用\\来向输出插入一个单斜杠。
转换标识符以%开始,接着是修饰控制符和转换字符。转换字符用于指定数据类型、分类、优先级、日期和线程名等。修饰控制符用于控制字段宽度、边距等。
    假设转换模式设置的为"%-5p [%t]: %m%n",Log4j2环境也设置使用PatternLayout,那么以下代码:
Logger logger = LogManager.getLogger("MyLogger");
logger.debug("Message 1");
logger.warn("Message 2");
    将会打印出的内容为:
DEBUG [main]: Message 1
WARN  [main]: Message 2
    在文本和转换标识之间没有明显的分隔符,模式解析器知道什么时候是转换标识的开始,什么时候是结束。在以上示例中,转换标识%-5p表示打印的内容需要向左对齐,宽度为5个字符。
    如果模式字符串没有包含处理Throwable的标识,模式的解析就会默认认为处理异常的转换标识%xEx已经添加到了字符串的末尾。如果完全不想讲异常日志打印出来,只需要添加标识%ex{0}就可以了。
以下为常见的转换标识:
(1)c{precision}或者logger{precision},输出日志事件logger的名字,precision是可选填的。当precision被指定一个整数时,它会剪切logger名字的长度。如果整数为正数,那么打印出来的日志就是从logger名字右边开始向左的整数位。如果整数为负数,则相反。
(2)C{precision}或class{precision},输出发起日志请求的类的全名,precision跟(1)类似。特别提醒,生成类的名字可能会影响到性能。
(3)d{pattern}或date{pattern},输出日志事件的日期。里面的pattern可以指定日期格式。
(4)enc{pattern}{[HTML|JSON]}或者encode{pattern}{[HTML|JSON]}用于对字符串进行编码,特别是字符串里包含特殊字符时这个功能就很有用,比如字符串中包含$${xxx}根据之前文章所描述的,Log4j2会对其进行解析,就会导致最终打印的日志出现问题。第一个选项填写对应的转换标识,第二个选项默认为html。比如%enc{%m}可以打印出正确的HTML格式日志,%enc{%m}{JSON}可以打印出正确的JSON格式日志。
(5)equals{pattern}{test}{substitution}或equalsIgnoreCase{pattern}{test}{substitution},先对pattern进行检测,若pattern检查不通过,则使用test替换substitution。
(6)ex|exception|throwable,用于输出异常,默认会输出完整的堆栈。
%throwable{short} 输出异常的第一行
%throwable{short.className} 输出异常抛出所在的类名
%throwable{short.methodName} 输出异常抛出的方法
%throwable{short.fileName} 输出异常抛出所在类的文件名
%throwable{short.lineNumber} 输出异常抛出的行数
%throwable{short.message} 输出异常消息
%throwable{short.localizedMessage} 输出本地信息
%throwable{n} 输出前n行堆栈
(6)f或者file输出请求日志的文件名,特别注意,可能会影响性能。
(7)K{key}、map{key}、MAP{key},若事件中存在MapMessage,则输出其中的分录。
(8)location输出日志事件的请求地位置信息,可能会影响性能。
(9)L或line,输出日志请求地的行数,可能会影响性能。
(10)m{nolookups}或msg{nolookups}或message{nolookups},输出日志事件中提供的消息。nolookups表示可以在打印的消息中包含类似$${xxx},且不会被解析。
(11)M或method,输出日志请求地的方法。
(12)p或者level,输出日志事件级别。
(13)replace{pattern}{regex}{substitution}从pattern中过滤出数据,查找其中的regex并替换为substitution。
(14)T或tid或threadId,输出发起日志请求的线程ID。
(15)t或tn或thread或threadName,输出发起日志请求的线程名称。



位置信息

    如果Layout配置了位置相关的pattern,比如%C、%F、%l、%L和%M,Log4j会维护一个堆栈的快照,并且遍历堆栈去查找位置信息。这是一个代价高昂的操作,对于同步logger而言,它会慢1.3到5倍。然而,异步的logger在传递日志信息到另一个线程之前也需要查找位置信息,而在传递给另一个线程后,位置信息会丢失,也就是另一个线程需要重新获取。所以,在异步logger中获取位置信息代价更高,如果获取位置信息,性能会慢30到100倍。因此,异步logger和appender默认是没有启用位置信息的,可以通过includeLocation="true"来进行启用。