按照基本的定义,日志即是对程序运行过程中关键事件的记录;大体日志分为运行日志和开发日志,运行日志在业务层面记录一些关键事件,为后面的跟踪运行提供帮助,而开发日志大多数时候是调试日志,根据事件流的输出来调试程序;因为开发人员本身的关注领域,运行日志可能制作的比较少,难以达到跟踪业务流的作用,而即使是开发日志,因为开发的调试有各种技巧,即使是跟踪事件流,使用println也比日志配置简单多了,这是一个投资回报的问ti,而人经常性的是短视的,调试可能在这些人眼里根本不需要认真对待,没有前期的事件记录规划,随着系统规模膨胀,执行流增多,执行事件保障,这时候没有一种合理的“开关”机制来选择查看感兴趣事件流,那结局是明晰的,日志不止提供了一种树形的开关结构,它还有灵活的输出控制,在Java常用日志框架Log4J还提供了JMX等接口,可以通过管理面板来监控和修改日志记录行为,这无疑是非常诱人的。
日志框架 | 支持日志级别 |
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 Logging 和SLF4J,在配置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有些不同的bridge方式,其形式类似适配,如下
这个结构有些复杂,这里是SLF4J到Log4J的桥接,我们看到Log4jLoggerFactory提供了类似的适配功能,这种方式的好处,按照官方文档的说法是提供了编译时绑定,排除了运行时的探测方式,性能会好一些,看着这图有些抽象,这里给出一个具体的应用场景,如下
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系的产品、框架都会使用这个日志框架,因此,我们也需要好好的学习一下,该日志结构如下
这里的也是一种适配器方式,只是最终暴露的使用接口是特定的LoggerProvider,查找特定日志框架的LoggerProvider是由LoggerProviders提供的,代码如下
代码显示在LoggerProviders加载时就开始查找特定LoggerProvider,具体查找逻辑如下
Jboss logging比较奇特的地方是其提供了一个JBossLogManagerLogger,这个Logger使用的内部Logger为JBoss容器托管日志管理器,这个可以注意一下
上面分析了日志框架前端Facade和后端实现如何适配,现在我们选取一个具体的框架来给出一些常见配置,希望能够对大家有用,我们选取的就是Log4J,因为其他框架我使用的频率比较低,二来因为日志框架的概念都类似,熟悉了一个其他的应该也可以快速掌握,下面让我们展开对Log4J的论述。
Log4J来自于Apache社区,是目前应用最广的一个日志框架,官方提供了两个大的版本,为Log4J 1.x和Log4J 2.x,这两个主版本连官网链接都不一样,看来Log4J 2.x应该针对前一版本问ti而重新设计,以至于导致和前一版本的不兼容,但因为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
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=' '>
<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,这个结构也比较一目了然,没有什么要讲的,大家可以根据自己的实际需要来调整结构。
这里还有个问ti是配置文件命名的问ti,默认情况下,如果配置文件命名为log4j.xml或log4j.properties时Log4J会自动加载,如果因为某种原因不能这样命名,那么久需要编码来实现配置的加载了,代码片段如下
"/app/resources/example.properties";
InitUsingPropertiesFile.class.getResource(resource);
PropertyConfigurator.configure(configFileResource);
日志框架就写这些吧,希望能对大家有所帮助,对单一常用日志框架的详细解析,有机会在写吧