Java日志框架研究及常见配置

【转载】url:http://blog.csdn.net/clannadyue/article/details/49123925
按照基本的定义,日志即是对程序运行过程中关键事件的记录;大体日志分为运行日志和开发日志,运行日志在业务层面记录一些关键事件,为后面的跟踪运行提供帮助,而开发日志大多数时候是调试日志,根据事件流的输出来调试程序;因为开发人员本身的关注领域,运行日志可能制作的比较少,难以达到跟踪业务流的作用,而即使是开发日志,因为开发的调试有各种技巧,即使是跟踪事件流,使用println也比日志配置简单多了,这是一个投资回报的问题,而人经常性的是短视的,调试可能在这些人眼里根本不需要认真对待,没有前期的事件记录规划,随着系统规模膨胀,执行流增多,执行事件保障,这时候没有一种合理的“开关”机制来选择查看感兴趣事件流,那结局是明晰的,日志不止提供了一种树形的开关结构,它还有灵活的输出控制,在Java常用日志框架Log4J还提供了JMX等接口,可以通过管理面板来监控和修改日志记录行为,这无疑是非常诱人的。
    大体上Java体系中比较常用的日志框架如下

日志框架

支持日志级别

Log4J

FATAL ERROR WARN INFO DEBUG TRACE

Java Logging API

SEVERE WARNING INFO CONFIG FINE FINER FINEST

Apache Commons Logging

FATAL ERROR WARN INFO DEBUG TRACE

SLF4J

ERROR WARN INFO DEBUG TRACE

Logback

ERROR WARN INFO DEBUG TRACE

Jboss logging

FATAL ERROR WARN INFO DEBUG TRACE

按照我比较有限的经验,我大多时候使用的是Log4J1.x版本,在配置一些开源框架时,可能需要到Apache Commons LoggingSLF4J,在配置Jboss系工具和框架的时候需要用到Jboss logging(例如Hibernate),其他日志框架如Java Logging,为Java自带的,功能上比较简单,用的也不是很多 ,而另一个Logback,这是一个号称Log4J继承者的新一代日志框架,因为我对“新一代”这个名词比较不感冒,我还没有怎么看,感兴趣的可以看看。
    基本上,日志级别是很相似的,这不仅仅是Java生态范围内,整个编程行业应该都有共识,通过日志级别来标记事件的轻重缓急,我们可以通过这个条件进行过滤,显示我们感兴趣的内容,关于日志级别之间的相互关系,这个比较简单,这里就不再说了。
    优秀的编程设计原则中有一条“依赖倒置”,要依赖抽象,不要依赖具体,具体的实践就是面向接口设计了,优秀的项目都会定义高层次抽象,通过封装隐藏多余的信息,这样在代码变动或者想不修改原有代码扩展的时候(开闭原则),能够相对容易的满足这些需求,而这些日志框架设计结构中则充分发挥了面向接口设计的优良传统,日志框架的核心接口是Logger类(接口),其他部分则是围绕此类(接口)展开,这里我们看两个定义例子,SLF4J的Logger定义如下

 
Apache Commons Logging的Log定义如下

 
因为其他的日志框架实在都太“重量级”了,方法太多,这里就不贴出定义了,日志框架定义了基本的接口,通过提供一个Facade,而后端由谁去实现就不那么重要了,这里面Apache Commons Logging、SLF4J、Jboss logging都容许其他后端实现为别的日志框架(大部分为Java Logging 、Log4J,差异化的部分在讲这些日志框架的时候再详细叙述),如何将Facade的请求转为后端可识别请求,知道一点设计模式的人应该马上想到适配器模式,这里,我们想给出几个日志框架如何实现这个接口适配的解决方法,我们一一道来。
     Apache Commons Logging
 
这是标准的适配器模式,没有什么好讲的,Apache Commons Logging提供的实际工作类如下
这里面我们比较熟悉的是JDK自带的Java Logging和Log4J,其他几个因为不怎么常用就不说了,关于Apache Commons Logging如何选择具体的实现类,这个可以从LogFactory的实现类中查找到逻辑,逻辑代码如下:
 
因为代码比较长,这里只贴出关键部分逻辑,可以看得很清楚,用户通过配置文件指定后端实现,反之通过查找类路径来选择具体实现类。
SLF4J
SLF4J也存在上述类似的适配方式,这里我们给出SLF4J有些不同的bridge方式,其形式类似适配,如下
 
这个结构有些复杂,这里是SLF4J到Log4J的桥接,我们看到Log4jLoggerFactory提供了类似的适配功能,这种方式的好处,按照官方文档的说法是提供了编译时绑定,排除了运行时的探测方式,性能会好一些,看着这图有些抽象,这里给出一个具体的应用场景,如下
   Spring日志配置
    Spring项目使用了Apache Commons Logging日志框架,因为该日志框架比较老旧,我们一般使用Log4J作为其后端实现,通过上面的介绍,我们知道只要将Log4J放入ClassPath中就可以了,如果使用maven,在依赖中添加Log4J就可以了,这里如何使用SLF4J的桥接,结果有些复杂,首先需要去掉Apache Commons Logging依赖,然后添加Spring到SLF4J的Bridge,然后使用一个后端实现Log4J,因此在添加一个SLF4J到Log4J的Bridge,最后是Log4J实现,用maven管理的话,结果如下
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.2.2.BUILD-SNAPSHOT</version>
        <exclusions>
            <exclusion>
                <!--排除commons-logging依赖-->
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <!--commons-logging到SLF4J之间的Bridge-->
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.5.8</version>
    </dependency>
<dependency>
        <!--SLF4J接口-->
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <!--SLF4J到Log4J之间的Bridge-->
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.5.8</version>
    </dependency>
    <dependency>
        <!--Log4J实现-->
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.14</version>
    </dependency>
</dependencies>
这种方式比较复杂,因为在平时工作中没有用过SLF4J日志框架,所以这种方式看起来有些多此一举,但考虑到SLF4J作为事实上的Java Logging标准,如何更加有效的在不同前端Facade Logger接口到后端实现之间自由适配,SLF4J提供了一个很好的形式,如下
 
前端到后端的适配能够更加方便,这确实是一个非常有趣的想法。
Jboss logging
    事实上这个日志框架貌似不是很出名,但是基本上Jboss系的产品、框架都会使用这个日志框架,因此,我们也需要好好的学习一下,该日志结构如下
 
这里的也是一种适配器方式,只是最终暴露的使用接口是特定的LoggerProvider,查找特定日志框架的LoggerProvider是由LoggerProviders提供的,代码如下
 
代码显示在LoggerProviders加载时就开始查找特定LoggerProvider,具体查找逻辑如下
上面看到的是通过配置文件查找
后半部分是根据ClassPath查找。
Jboss logging比较奇特的地方是其提供了一个JBossLogManagerLogger,这个Logger使用的内部Logger为JBoss容器托管日志管理器,这个可以注意一下
 
上面分析了日志框架前端Facade和后端实现如何适配,现在我们选取一个具体的框架来给出一些常见配置,希望能够对大家有用,我们选取的就是Log4J,因为其他框架我使用的频率比较低,二来因为日志框架的概念都类似,熟悉了一个其他的应该也可以快速掌握,下面让我们展开对Log4J的论述。
    Log4J来自于Apache社区,是目前应用最广的一个日志框架,官方提供了两个大的版本,为Log4J 1.x和Log4J 2.x,这两个主版本连官网链接都不一样,看来Log4J 2.x应该针对前一版本问题而重新设计,以至于导致和前一版本的不兼容,但因为Log4J 1.x普及的根深蒂固,很多日志框架没有及时跟上对Log4J 2.x后端的支持,Log4J 2.x彻底替换掉Log4J 1.x还有很长的一段路要走。
    这里我们给出的是Log4J 1.x的版本介绍,关于Log4J 2.x,我们有机会再进行探索;在Log4J的体系里,有三个最重要的概念:Logger、Appender、Layout,Logger即为日志写入入口,Appender定义了将日志时间输出的策略,Layout给出输出的格式,三者的关系如下
 
因为Log4J中有Category的概念,这里我把它看成一个Logger,三者的关系这样看起来比较明显,Logger接收日志记录,Appender接收Logger发来的日志事件,然后根据Layout定义格式进行输出,这里没有什么好讲的,我们开始讲如何配置。
    Log4J 1.x有两种配置方式,一种是Java properties文件,一种是XML,第一种用的比较多一些,这个和Log4J 2.x正好相反,配置文件配置主要配置上面三个对象,配置Logger,然后配置使用哪种具体Appender,然后定义格式,看一个例子,如下:
 
log4j.rootLogger=DEBUG, A1
 
log4j.appender.A1=org.apache.log4j.ConsoleAppender
 
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
 
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
如上,一定要定义的Logger是root Logger,这里构成了一个类似树形结构,顶部节点定义默认行为,子节点可以对行为进行修改定制,这里定义rootLogger使用DEBUG级别,使用A1这个Appender,注意这里的写法:第一个为日志级别,后面可以跟很多个Appender;线面定义了这个具体的Appender A1,我们看到其使用的是ConsoleAppender,后面我们给这个Appender指定Layout,这里的Layout为PatternLayout,这个比较常用,最后一行定义了日志格式,这种格式写法类似于printf那种格式化字符串,里面的占位符构成为"%[格式修饰符]日志含义符号"(中括号里的为可选),格式修饰符比较简单,“-”代表靠左对其,不加就是靠右对其,后面的数字代表了该日志内容部分的最少显示长度,如果有“.”,"."之后为该日志内容最长显示长度,超过长度会进行裁短;后面的日志含义符号如下表
 

符号

意义

c(小写)

输出日志的类别

C(大写)

输出触发日志请求的类的全称

d

输出日志事件的日期时间

F

输出日志请求类文件名

l

输出请求日志调用位置信息

L

输出请求日志代码行号

m

输出应用提供的日志消息

M

输出请求日志方法名

n

输出平台特定的行分隔符

p

输出日志级别

r

输出从程序启动到现在的毫秒数

t

输出生成这个日志事件线程的名字

 
这里在给出一个例子
log4j.rootLogger=DEBUG, A1
 
log4j.appender.A1=org.apache.log4j.ConsoleAppender
 
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%p [%t] %c{2} (%M:%L) - %m%n
 
log4j.logger.org.apache.log4j.examples=INFO, A2
 
log4j.appender.A2=org.apache.log4j.FileAppender
log4j.appender.A2.File=${user.home}/test
 
#如果test文件存在就先清空它
log4j.appender.A2.Append=false
 
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%5r %-5p [%t] %c{2} - %m%n
上面的例子稍微复杂一些,这里设定了一个rootLogger,并制定了其日志级别和Appender,后面定义了一个名为org.apache.log4j.examples 的Logger,这个Logger又定义了日志级别和Appender,通过这里的例子可以看出Log4J这种树形的关系具有足够的灵活性。
    关于XML的配置,这里给出一个实例,大家可以根据上面对properties的理解来配置XML,如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
        </layout>
    </appender>
    <category name="org.apache.log4j.xml">
        <priority value="info" />
    </category>
    <Root>
        <priority value ="debug" />
        <appender-ref ref="STDOUT" />
    </Root>
</log4j:configuration>
这里的category如前面分析结构时所说,可以认为其就是一个Logger,这个结构也比较一目了然,没有什么要讲的,大家可以根据自己的实际需要来调整结构。
这里还有个问题是配置文件命名的问题,默认情况下,如果配置文件命名为log4j.xml或log4j.properties时Log4J会自动加载,如果因为某种原因不能这样命名,那么久需要编码来实现配置的加载了,代码片段如下
String resource =
                "/app/resources/example.properties";
        URL configFileResource =
                InitUsingPropertiesFile.class.getResource(resource);
        PropertyConfigurator.configure(configFileResource);
 
日志框架就写这些吧,希望能对大家有所帮助,对单一常用日志框架的详细解析,有机会在写吧
原文地址:https://www.cnblogs.com/shizi1987/p/8144484.html