Java日志框架

日志框架

slf4j

slf4j是一个面向java Logging框架的开源包,其将各个不同的日志框架抽象成一套统一的API进行操作。slf4j不参与具体的日志代码实现,仅仅是根据程序的配置来绑定具体的日志系统。因此,基于slf4j可以让日志使用独立于具体的日志框架。

如上图,slf4j配合具体日志框架的桥接可以在多个日志框架之间切换。

 

扩展:Java日志框架的行号是怎么获取到的?

用过很多框架,如log4j,logback等。每种框架都支持在日志中输出行号,那框架是如何得到行号的呢?

以logback来说。

在发起logger.info("xxx");调用时,会build一个LoggingEvent对象。在此对象中有获取当前当前调用栈的方法:

1     public StackTraceElement[] getCallerData() {
2         if (callerDataArray == null) {
3             callerDataArray = CallerData
4                             .extract(new Throwable(), fqnOfLoggerClass, loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
5         }
6         return callerDataArray;
7     }

在这里,通过new Threwable(),然后遍历StackTraceElement来获取当前调用有效栈。因为堆栈是从当前点往外逐层叠加的,那遍历栈时判断栈归属类是否为ch.qos.logback.classic.Logger.class.getName() || org.apache.log4j.Category || org.slf4j.Logger || 是否在框架自身的package内。当跳出此循环后,即定位到日志输出行。在StackTraceElement中有lineNumber记录,直接读取即可得到此行号。此过程如下:

 1     /**
 2      * Extract caller data information as an array based on a Throwable passed as
 3      * parameter
 4      */
 5     public static StackTraceElement[] extract(Throwable t, String fqnOfInvokingClass, final int maxDepth, List<String> frameworkPackageList) {
 6         if (t == null) {
 7             return null;
 8         }
 9 
10         StackTraceElement[] steArray = t.getStackTrace();
11         StackTraceElement[] callerDataArray;
12 
13         int found = LINE_NA;
14         for (int i = 0; i < steArray.length; i++) {
15             if (isInFrameworkSpace(steArray[i].getClassName(), fqnOfInvokingClass, frameworkPackageList)) {
16                 // the caller is assumed to be the next stack frame, hence the +1.
17                 found = i + 1;
18             } else {
19                 if (found != LINE_NA) {
20                     break;
21                 }
22             }
23         }
24 
25         // we failed to extract caller data
26         if (found == LINE_NA) {
27             return EMPTY_CALLER_DATA_ARRAY;
28         }
29 
30         int availableDepth = steArray.length - found;
31         int desiredDepth = maxDepth < (availableDepth) ? maxDepth : availableDepth;
32 
33         callerDataArray = new StackTraceElement[desiredDepth];
34         for (int i = 0; i < desiredDepth; i++) {
35             callerDataArray[i] = steArray[found + i];
36         }
37         return callerDataArray;
38     }

扩展日志框架在日志内容中插入自定义的内容时,需要注意框架包声明。

因为是在扩展中调用具体日志框架实例,那日志输出的行号会定位到你的扩展实现中,此时丢失业务日志输出行号。从上述逻辑可以看出,在定位有效堆栈的时候,框架package是一个过滤条件,那将扩展所在的package加入框架package中即可解决此问题。

相比声明框架包外,使用标准的MDC是更推荐的一种实现方式。支持MDC的日志框架只有logback与log4j。其余的日志框架,在slf4j下会走到空处理,需要通过代码从MDC中读出来。

MDC本质是一个维护一个ThreadLocal对象,存储每个线程的上下文,然后在日志文件中用%X{param}读取变量并输出到日志文件中。

这样看来,日志要获取输出点行号是比较复杂的一个过程,日志输出如果过多,节点资源损耗预计比较可观。

posted @ 2017-12-20 17:11  飞昂之雪  阅读(287)  评论(0编辑  收藏  举报