三方库--slf4j-log4j的使用
本文不去探究日志门面 slf4j 与其他众多日志框架 log4j、log4j2、logback、j.u.l(java.util.logging) 之间的关系,我们将重点关注 slf4j 与 log4j 的组合使用。
log4j 应该是最经典、使用人数最多的日志框架。博主刚好在最近开发的一个项目中使用到了 slf4j 与 log4j,期间发现了不少问题,遂决定将 slf4j-log4j 在项目中的使用进行一个简单的记录并分享。
log4j与log4j2
据我了解,log4j 已经不再更新,引用来自官网的一段话:
End of Life On August 5, 2015 the Logging Services Project Management
Committee announced that Log4j 1.x had reached end of life. For
complete text of the announcement please see the Apache Blog. Users of
Log4j 1 are recommended to upgrade to Apache Log4j 2.
是的,log4j 停止于 1.x 版本,迎来了 log4j 2。我们不应该停止对新版本的探索,何况旧版本已不再进行更新。log4j2 较 log4j 整体有了较大改进,这也是我们需要去了解它的主要原因。log4j 和 log4j2 的不同之处主要体现在以下方面:
- 不再支持 properties 文件做为配置文件;
- 不支持 Java6 以下的版本;
- 支持插件、过滤器等;
- 相比 1.x,性能大幅提升。
关于 log4j2 更详细的内容,有兴趣的同学可以自行了解,现在开始讲述本文的主题 — slf4j-log4j 的使用。
log4j与slf4j的组合使用
导入Maven依赖
如今大部分项目都基于 Maven 所创建,因此在 IDEA 中导入 jar 包这种比较过时的操作不再讲述,有需要的同学可以自行 Google。我们在 pom.xml 文件中添加如下依赖项:
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
slf4j-log4j12 这个包肯定依赖了 slf4j 和 log4j,所以使用 slf4j-log4j 的组合只要配置上面这一个依赖就够了。
log4j.properties配置文件
log4j 的配置文件可以基于 .properties 文件,也可以基于 .xml 文件,我习惯使用 .properties 文件,因此本文只探讨基于 .properties 文件进行 log4j 的配置,不过正如上文所述,.properties 文件在 log4j2 中已被弃用,原因是 .xml 文件的可读性更好(我怎么不这样觉得)?
在 Maven(由 Maven 管理) 项目中,log4j.properties 配置文件(文件名就是如此)放在 src 目录下的 resources 目录中即可,系统便会自动加载配置文件。至于在 Spring 框架中,Web 项目中,亦或是在普通 Java 项目中 log4j.properties 文件的放置路径如何,博主并未进行考证,因此也就不再多说,有需要的同学自行 Google。
log4j.properties内容
log4j日志等级
在对 log4j 进行配置前,我们先要了解下 log4j 的日志等级。日志等级就是日志的重要程度,log4j 日志分为 7 个等级:ALL、DEBUG、INFO、WARN、ERROR、FATAL、OFF,从左到右等级由低到高,我们最常用的 4 个等级为 INFO、WARN、ERROR、FATAL。分等级是为了设置日志输出的门槛,只有等级等于或高于这个门槛的日志才有机会输出。
ALL:各级包括自定义级别;
DEBUG:指定细粒度信息事件;
INFO:应用程序运行情况粗粒度级别的信息;
WARN:指定具有潜在危害的情况;
ERROR:错误事件可能仍然允许应用程序继续运行;
FATAL:指非常严重的错误事件,可能导致应用程序中止;
OFF:最高等级,关闭日志记录。
log4j日志实例
日志实例,就是代码里实例化的 Logger 对象:
log4j.rootLogger=LEVEL,appenderName1,appenderName2,...
这是全局 logger 的配置,LEVEL 用来设定日志等级并且只能设定一个日志等级,appenderName 定义日志输出器,日志输出器可同时定义多个。
appender日志输出器
日志输出器指定 logger 的输出位置:log4j.appender.appenderName=className
。
输出位置有 5 种选择:
org.apache.log4j.ConsoleAppender # 控制台
org.apache.log4j.FileAppender # 文件
org.apache.log4j.DailyRollingFileAppender # 每天产生一个日志文件
org.apache.log4j.RollingFileAppender # 文件大小到达指定尺寸的时候产生一个新的文件
org.apache.log4j.WriterAppender # 将日志信息以流格式发送到任意指定的地方
每种 appender 都有若干配置项,我这里只介绍常用的三种:
ConsoleAppender
Threshold=INFO # 指定日志信息的最低输出级别,默认DEBUG
ImmediateFlush=true # 表示所有消息都会被立即输出,设为false则不输出,默认值是true
Target=System.err # 默认值是System.out
DailyRollingFileAppender
Threshold=WARN # 指定日志信息的最低输出级别,默认DEBUG
ImmediateFlush=true # 表示所有消息都会被立即输出,设为false则不输出,默认true
Append=false # true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认true
File=D:/logs/logging.log4j # 指定当前消息输出到logging.log4j文件
DatePattern='.'yyyy-MM # 每月滚动一次日志文件,即每月产生一个新的日志文件。当前月的日志文件名为logging.log4j,前一个月的日志文件名为logging.log4j.yyyy-MM
# 另外,也可以指定按周、天、时、分等来滚动日志文件,对应的格式如下:
# 1)'.'yyyy-MM:每月
# 2)'.'yyyy-ww:每周
# 3)'.'yyyy-MM-dd:每天
# 4)'.'yyyy-MM-dd-a:每天两次
# 5)'.'yyyy-MM-dd-HH:每小时
# 6)'.'yyyy-MM-dd-HH-mm:每分钟
RollingFileAppender
Threshold=WARN # 指定日志信息的最低输出级别,默认DEBUG
ImmediateFlush=true # 表示所有消息都会被立即输出,设为false则不输出,默认true
Append=true # true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认true
File=D:/logs/logging.log4j # 指定消息输出到logging.log4j文件
MaxFileSize=100KB # 后缀可以是KB,MB或者GB。在日志文件到达该大小时,将会自动滚动,即将原来的内容移到logging.log4j.1文件
MaxBackupIndex=2 # 指定可以产生的滚动文件的最大数,例如,设为2则可以产生logging.log4j.1,logging.log4j.2两个滚动文件和一个logging.log4j文件
layout输出内容及格式
layout 指定 logger 输出内容及格式:log4j.appender.appenderName.layout=className
。
layout 有 4 种选择:
org.apache.log4j.HTMLLayout # 以HTML表格形式布局
org.apache.log4j.PatternLayout # 可以灵活地指定布局模式
org.apache.log4j.SimpleLayout # 包含日志信息的级别和信息字符串
org.apache.log4j.TTCCLayout # 包含日志产生的时间、线程、类别等信息
layout 也有配置项,同样我们只讲最常用的 PatternLayout 配置:
ConversionPattern=%m%n # 设定以怎样的格式显示消息
设置格式的参数说明如下:
%p:输出日志信息的优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%d:输出日志时间点的日期或时间,默认格式为ISO8601,可以指定格式如:%d{yyyy/MM/dd HH:mm:ss,SSS}
%r:输出自应用程序启动到输出该log信息耗费的毫秒数
%t:输出产生该日志事件的线程名
%l:输出日志事件的发生位置,相当于%c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数
%c:输出日志信息所属的类目,通常就是类全名
%M:输出产生日志信息的方法名
%F:输出日志消息产生时所在的文件名
%L:输出代码中的行号
%m:输出代码中指定的具体日志信息
%n:输出一个回车换行符,Windows平台为"rn",Unix平台为"n"
%x:输出和当前线程相关联的NDC(嵌套诊断环境)
%%:输出一个"%"字符
slf4j-log4j在Java代码中的使用
原来使用 log4j 在 Java 中是这样声明 logger 的:
public class Log4jTest {
private static final Logger LOGGER = Logger.getLogger(Log4jTest.class);
}
现在只要改成这样就行了:
public class Slf4jTest {
private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
}
在同一类中输出多个日志文件
考虑如下代码:
private static final Logger LOGGER1 = LoggerFactory.getLogger("test1");
private static final Logger LOGGER2 = LoggerFactory.getLogger("test2");
我们想通过在同一个类中创建两个不同的日志实例而将此类产生的日志信息输出到不同的地方,那么可以这样配置 log4j.properties:
log4j.logger.test1=DEBUG, test11
log4j.appender.test11=org.apache.log4j.FileAppender
log4j.appender.test11.File=${myweb.root}/WEB-INF/log/test1.log
log4j.appender.test11.layout=org.apache.log4j.PatternLayout
log4j.appender.test11.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.logger.test2=DEBUG, test22
log4j.appender.test22=org.apache.log4j.FileAppender
log4j.appender.test22.File=${myweb.root}/WEB-INF/log/test2.log
log4j.appender.test22.layout=org.apache.log4j.PatternLayout
log4j.appender.test22.layout.ConversionPattern=%d %p [%c] - %m%n
也就是在用 logger 时给它一个自定义的名字(如这里的"test1"),但有一个问题,就是这些自定义的日志默认是同时输出到 log4j.rootLogger 所配置的日志中的,如何只让它们输出到自己指定的日志中呢?log4j.rootLogger
下添加 log4j.additivity.org.apache=false
即可。
log4j分级输出日志文件
在使用 log4j 的时候,大家应该都有这样一个需求:只有 INFO 级别的日志打印到控制台,WARN 级别的日志打印到 warn.log 文件,ERROR 级别的日志打印到 error.log 文件,我暂且将这样的需求定义为 log4j 日志文件的分级输出。对于这样的需求,我们来看下面这个完整的配置文件是否能够将其满足:
log4j.rootLogger=INFO,infoConsole,warnFile,errorFile
log4j.additivity.org.apache=false
# infoConsole
log4j.appender.infoConsole=org.apache.log4j.ConsoleAppender
log4j.appender.infoConsole.Threshold=INFO
log4j.appender.infoConsole.ImmediateFlush=true
log4j.appender.infoConsole.Target=System.out
log4j.appender.infoConsole.layout=org.apache.log4j.PatternLayout
log4j.appender.infoConsole.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c.%M [%t] - %m%n
# warnFile
log4j.appender.warnFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.warnFile.Threshold=WARN
log4j.appender.warnFile.ImmediateFlush=true
log4j.appender.warnFile.Append=true
log4j.appender.warnFile.File=${filePath}/warn.log
log4j.appender.warnFile.DatePattern='.'yyyy-MM-dd
log4j.appender.warnFile.layout=org.apache.log4j.PatternLayout
log4j.appender.warnFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %l [%t] - %m%n
# errorFile
log4j.appender.errorFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorFile.Threshold=ERROR
log4j.appender.errorFile.ImmediateFlush=true
log4j.appender.errorFile.Append=true
log4j.appender.errorFile.File=${filePath}/error.log
log4j.appender.errorFile.DatePattern='.'yyyy-MM-dd
log4j.appender.errorFile.layout=org.apache.log4j.PatternLayout
log4j.appender.errorFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %l [%t] - %m%n
答案是不能。它会将 INFO 级别的日志输出到控制台,然后将 WARN 级别的日志同时输出到控制台以及 warn.log 中,对于 ERROR 级别日志信息的输出情况我并未实践,但可以推测,它将会把日志信息输出到控制台、warn.log 以及 error.log 中。
产生这个问题的原因,我觉得是由于 log4j.rootLogger=INFO
全局配置造成的,全局配置中的日志等级只能指定一个,我指定了 INFO。因此当产生 WARN 级别日志的时候,会先与全局配置中的 INFO 级别做比较,等级大于 INFO,开始依次匹配三个日志输出器的配置。我们在 infoConsole 中设置 log4j.appender.infoConsole.Threshold=INFO
,warnFile 中设置 log4j.appender.warnFile.Threshold=WARN
,都会被 WARN 级别日志成功匹配,因此 WARN 级别的日志会同时输出至控制台以及 warn.log 文件。ERROR 级别日志信息的输出情况应与此同理。
那怎么解决这个问题呢?我们只需要给每个日志输出器中添加如下配置项:
# warnFile
log4j.appender.warnFile.filter.warnFilter=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.warnFile.filter.warnFilter.LevelMin=WARN
log4j.appender.warnFile.filter.warnFilter.LevelMax=WARN
# errorFile
log4j.appender.errorFile.filter.errorFilter=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.errorFile.filter.errorFilter.LevelMin=ERROR
log4j.appender.errorFile.filter.errorFilter.LevelMax=ERROR
关于这些配置项的详细解释我不再贴出,有需要的同学自行 Google。
在log4j.properties中使用相对路径
再考虑一个使用场景:假设我现在想开发一个第三方库,库中使用 slf4j-log4j 日志框架,则我们日志文件的输出路径需要保证在不同操作系统环境下不会发生错误;除此之外,我们编写的应用程序也常会被打包成 .war 或 .java 被发布至不同的应用环境上。此时,配置 log4j.properties 时,需要考虑两件事情:
- 日志文件输出的路径应怎样指定才能保证在不同的环境下始终有效?
- 不同操作系统如:Windows 系统中的文件路径:
D:\logs\warn.log
,类 Unix 系统中的文件路径:/home/logs/warn.log
,两种不同的路径表示方式应如何统一?
答案是使用相对路径表示法。想要在 log4j.properties 中使用相对路径,我们首先需要在代码中设定系统环境变量,如下:
// 通过user.dir得到当前工作目录
String rootPath = System.getProperty("user.dir");
System.setProperty("log.base", rootPath);
然后在 log4j.properties 中指定相对路径:${log.base}/logs/warn.log
。但这只解决了第一个问题,对于第二个问题,应该如何解决?
据我查询到的资料所述,现如今不同的操作系统会将路径自动转换。也就是说,如果我们指定的相对路径为 ${log.base}/logs/warn.log
,那么在 Windows 操作系统下,输出路径会自动加上所在盘符,并将路径中的 /
修改为 \
,这主要是因为它们利用了 File 类来处理文件路径。因此统一路径写法非常简单,即只保留 Linux(Unix 类)路径写法就行,这样大多数程序都能正常工作。但有少量程序是不支持统一路径写法的(可能是因为它们没有使用 File 类来处理文件路径,而是手工拼接),这时应怎么办呢?因 Java 中能判断操作系统版本,故可以考虑写个函数,将统一路径写法转为当前操作系统的格式,详细内容可以参考此博客:[Java] Windows/Linux路径不同时,统一war的最简办法。
总结
- 熟悉 log4j2,尝试将 log4j 项目平滑过渡至 log4j2;
- 熟悉 log4j 配置文件的编写方法;
- 熟悉 log4j 如何配置分级输出;
- 熟悉 log4j 如何统一路径写法;
- 熟悉 log4j 如何配置定时清除日志文件 — 只保留一周内日志信息(本文未予讨论,但很重要)。
参考阅读
上一篇: kibana操作ES
下一篇: FF4J(特性框架)简介及入门