容易被忽略的数据生产源
目前,随着大数据方向的推进,越来越多的数据被应用于数据分析和挖掘,而其中一大部分就是项目中的 日志数据。而 Java 项目中有很多的日志输出包,不同项目使用不同的日志工具会造成数据结构的不一致,这样就为 数据分析 增添了一定的麻烦,今天记录下对各中日志工具的说明。
日志组件历史
JUL
这个是 java.util.logging 的缩写,也就是 Java 本身 JDK 自带的日志工具,但是通常它的功能有限,因此,项目中的日志输出都是采用特有的日志工具进行记录。而日志工具中得到广泛使用的就是 log4j.
Log4j
Java 界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j, 许多应用程序的日志部分都交给了 log4j, 不过作为组件开发者,他们希望自己的组件不要紧紧依赖某一个工具,毕竟在同一个时候还有很多其他很多日志工具,假如一个应用程序用到了两个组件,恰好两个组件使用不同的日志工具,那么应用程序就会有两份日志输出了。
JCL
为了解决这个问题,Apache Commons Logging ( 之前叫 Jakarta Commons Logging, 所以缩写为 JCL )粉墨登场,JCL 只提供 log 接口,具体的实现则在运行时 动态寻找。这样一来组件开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。
所以即使到现在你仍会看到很多程序应用 JCL + log4j 这种搭配,不过当程序规模越来越庞大时,JCL 的 动态绑定 并不是总能成功,具体原因大家可以 Google 一下,这里就不再赘述了。解决方法之一就是在程序部署时 静态绑定 指定的日志工具,这就是 SLF4J 产生的原因。
SLF4j
跟 JCL 一样,SLF4J 也是只提供 log 接口,具体的实现是在打包应用程序时所放入的绑定器( 名字为 slf4j-XXX-version.jar
)来决定,XXX
可以是 log4j12, jdk14, jcl, nop 等,他们实现了跟具体日志工具( 比如 log4j )的绑定及代理工作。举个例子:如果一个程序希望用 log4j 日志工具,那么程序只需针对 slf4j-api 接口编程,然后在打包时再放入 slf4j-log4j12-version.jar 和 log4j.jar 就可以了。
现在还有一个问题,假如你正在开发应用程序所调用的组件当中已经使用了 JCL 的,还有一些组建可能直接调用了 java.util.logging,这时你需要一个桥接器( 名字为 XXX-over-slf4j.jar )把他们的日志输出重定向到 SLF4J, 所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到” SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。过程如下。
这时,你可能会发现一个有趣的问题,假如在 CLASS_PATH 里同时放置 log4j-over-slf4j.jar 和 slf4j-log4j12-version.jar 会发生什么情况呢?没错,日志会被踢来踢去,最终进入死循环。
日志搭配组合
日志工具那么多,有门面也有具体实现,那到底如何进行搭配呢?这里主要给出目前最流行的两种搭配:
JCL + Log4j 搭配
这种方式是采用 JCL 作为日志门面抽象接口,具体日志输出使用 Log4j. 具体用到的 Jar 包和资源文件如下:
1 | 1. commons-logging-1.1.jar // JCL 日志门面 |
对于 Log4j.properties 如何配置,下面会讲到,这里给出常用日志定义代码:
1 | // 注意导入的包 |
SLF4j + Log4j 搭配
这种方式采用 SLF4j 作为日志门面抽象接口,具体日志输出仍然使用 Log4j. 具体用到的 Jar 包和资源文件如下:
1 | 1. slf4j-api-1.5.11.jar |
我们可以看到除了各自的 API jar 包 还有一个 slf4j-log4j12-1.5.11.jar, 这个就是输出流重定向的意思,将 slf4j 接口输出转到具体的 log4j 实现。而假如你目前项目中已经用 JUL 实现日志输出了,你想用此种配置方式怎么办?那就再加一个 jar 包:
1 | jul-to-slf4j-1.7.25.jar |
或者你已经使用了 JCL 日志门面接口,那如何转,只要加下面的 jar 包:
1 | jcl-over-slf4j-1.7.25.jar |
从中我们可以看出,slf4j 接口使用还是很广泛的,不管是入口还是出口都有各种对应的 jar 包可供使用的,那它可定制化以及适应性是非常广泛的,因此我推荐大家以后尽量使用 SLF4j 这个日志门面作为通用日志输出接口。
使用 SLF4j 的日志代码:
1 | // 注意导入的包和上面的 JCL 不一样的,不要混淆了 |
这里我们发现一个不同点,就是 SLF4j 可以用 {}
作为占位符,进行日志字符串的拼接操作,那这个有什么好处呢?这里也说明下:
首先看不用占位符是怎么使用多字符串拼接的:
1 | logger.debug("This is debug: " + "debug stack string"); |
如上所示,完成了一个 debug 日志输出,很多人都是这样实现的,但是大家知道,我们上线的应用不能将 debug 日志输出的,因为 debug 只能在开发调试阶段使用。因此,我们需要配置我们的日志工具,使其只能输出 info, warn, error 的日志信息。那么,logger.debug
这句话内部就会自动判断是否要进行输出,当在内部判断后确实不需要输出!
但是,我们发现一个问题,就是参数字符串拼接都是要先执行的,也就是不管你内部要不要输出,字符串都是要先拼接好才能进入 logger 内部判断的。如果日志记录很少有加的字符串还没多少性能问题,但如果有很多字符串拼接操作,并且拼接很多个字符串,那么会白白地浪费这些字符串拼接过程的性能。因此,正确的做法是:
1 | if (logger.isDebugEnabled()){ |
但是没次输出都要先进行判断是不是太过于重复了,因此,带占位符的字符串拼接操作就诞生了:
1 | logger.debug("This is debug: {}" , "debug stack string"); |
这种方式多个字符串当做参数传入,不会先进行拼接再传入,而是在内部判断后再进行拼接操作,因此这也是 SLF4j 日志工具的一大优势。那下面就主要讲下 SLF4j 的配置参数。
SLF4j 配置
SLF4j 由于其适配广泛,通用性强,因此很多开源项目中都是使用它作为自己的日志记录接口,就如 Hadoop 系列生态。我们在开发 Hadoop 生态应用的时候,常常会在调试的时候打印出:
1 | log4j:WARN No appenders could be found for logger (org.apache.hadoop.util.Shell). |
这个就是因为没有配置 log4j.properties 所导致的。那通常的解决方法就是在项目路径里新建一个 log4j.properties, 然后填下面信息就可以了:
1 | log4j.rootLogger=INFO, stdout, logfile |
那这些配置信息到底是什么意思?下面详细讲下。
配置 RootLogger
首先看第 1 行,也就是 log4j.rootLogger
的配置,其语法为:
1 | log4j.rootLogger = [ level ] , appenderName1, appenderName2, ... |
(1). level : 是日志记录的优先级,分为 OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL 或者您定义的级别。Log4j 建议只使用四个级别,优先级从高到低分别是 ERROR、WARN、INFO、DEBUG. 如果设置为 INFO 则,ERROR, WARN, INFO 都会输出,而 DEBUG 不会输出。
(2). appenderName : 是日志输出的目的地,名字是 自定义,也可以写多个;上面的配置 stdout 就是一个 appenderName 名字,当然你也可以叫其他名字的。当然你这里定义了这个名字,那么下面就要配置这个名字对应的输出地的相关信息,那下面配置的信息就得和这里设置的一致。下面会讲到。
配置信息输出目的地
上面代码的第 4-7 和 12-16 行都是配置日志输出目的地的,目的地有多种类型:
(1). org.apache.log4j.ConsoleAppender(控制台)
1 | Threshold=WARN // 指定日志消息的输出最低层次。 |
(2). org.apache.log4j.FileAppender(文件)
1 | Threshold=WARN // 指定日志消息的输出最低层次。 |
(3). org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
1 | Threshold=WARN // 指定日志消息的输出最低层次。 |
(4). org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
1 | Threshold=WARN // 指定日志消息的输出最低层次。 |
(5). org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
这个用得相对较少,这里就不介绍了。
日志信息的输出格式
上面代码的第 8-9 和 17-18 行都是配置日志输出格式的,也有多种类型:
(1). org.apache.log4j.HTMLLayout(以HTML表格形式布局)
1 | LocationInfo=true // 默认值是 false, 输出 java 文件名称和行号 |
(2). org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
1 | ConversionPattern=%m%n // 指定怎样格式化指定的消息。 |
(3). org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
1 | LocationInfo=true:默认值是false,输出java文件和行号 |
(4). org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
对于 PatternLayout 模式下:
ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
这里需要说明的就是日志信息格式中几个符号所代表的含义:
1 | -x号: x信息输出时左对齐; |
下面给出一个完整的配置说明:
1 | # 这里里配置了DEBUG等级,则可显示DEBUG以上的所有信息; |