文章目录
零、一些问题的修改与更新
0.1 将日志自动保存到不同的日志文件(19-10-03更新)
· 自己在做项目的时候遇到了一个问题:重新运行项目,如何把日志保存到不同的log文件?
· 我之前写的那个模板有一个重大的问题:每次运行程序,只要日志文件没超过maxSize,log都只会记录到这个文件。但是调试的时候,这种操作令人十分不爽,因此,在查询了官网之后,给模板进行了修改。
修改如下:
· 因为在普通的字符串里无法使用pattern的语法,因此使用fileName="$${date:MM-dd-HH-ss}"
作为保存的日志名称,这样每次运行程序,日志必然会保存在不同的文件下。
· 实际上,我们完全可以构造两个FileAppender,这样就可以根据我们的需要来使用不同的日志文件记录方式了:本文的模板已更新,不喜可删。
一、什么是slf4j和log4j2
· 目前开源的日志框架有很多,如logback,log4j等,这面临了一个问题:当使用不同的日志框架或者这些日志框架面临了重大更新的时候,如果其实现函数有所改变,那么旧版本的框架就会崩溃,这很不利于项目的维护和新框架的推广。因此才诞生了例如slf4j这样的日志门面。
· 日志门面,顾名思义就是日志框架对外的表现形式,理论上只要日志框架实现了日志门面的接口,不管其怎么更新,开发者都不需要对项目的依赖,或者项目的代码做出过多的改变。目前还在坚持更新且性能良好的日志门面非slf4j莫属,因此强烈建议使用slf4j作为我们书写日志的接口。
· 日志框架方面,logback是log4j的升级版,这两个框架与slf4j是一个开发者开发出来的,因此其整合十分之稳定。但是log4j2作为apache最新推出的日志框架,在异步记录日志的方面性能比logback要更加的优异,因此本着要用就用最好的的原则 ,使用lo4gj2作为日志记录的框架。
注:log4j2只是假借了log4j之名而已,log4j的真正升级版是logback。
二、 依赖
· 日志的环境依赖总共有4部分,分别是slf4j接口部分,log4j2的日志框架实现部分,log4j2异步日志插件部分以及slf4j与log4j2的桥接部分。
包名 | 作用 |
---|---|
slf4j-api | 目前的版本1.7就可。slf4j的接口包,我们对日志进行的所有操作都用的是这个包的API |
log4j-core | 版本必须在2.1以上,是log4j2的核心包 |
log4j-api | 版本与核心包一致,log4j2的接口包,用来直接调用log4j2框架的,必须导入,不然无法与slf4j对接 |
log4j-web | 版本与核心包一致,用于web项目的log4j2,阻止web项目出现警告 |
com.lmax.disruptor | 版本随意,开启log4j2的异步日志记录的功能 |
log4j-slf4j-impl | 版本与核心包一致,用于slf4j与log4j对接,即用slf4j的接口实现操作日志 |
这里给出一份Maven完整依赖:
<properties>
<log4j2.version>2.11.2</log4j2.version>
<slf4j.version>1.7.28</slf4j.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-web -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>${log4j2.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
三、配置
· 看多很多的博客,都没有官网的配置全面,因此给出官网传送门:log4j2官网详细配置说明
· 本博客给出一种粘贴复制即可用的配置,并给出一些个人认为比较重要的说明。
3.1 配置文件
· 基本上所有的配置都写在了Properties里,因此,只需要改Properties就可以用了(建议自定义Logger,不建议修改Appenders)。
Apperders里定义了两种日志记录的格式:1. 在控制台输出name=“Console”,2. 在文件输出name=“File”。使用RollingRandomAccessFileAppender是因为其性能最好,且支持日志自动归档。
<?xml version="1.0" encoding="UTF-8"?>
<!-- https://www.cnblogs.com/hafiz/p/6170701.html-->
<Configuration status="WARN" name="MyApp">
<Properties>
<!--日志文件保存的路径-->
<property name="filePath" value="src/logs/$${date:MM-dd-HH-mm-ss}-untitled.log"/>
<property name="filePath_onlyOne" value="src/logs/untitled.log"/>
<!--日志文件归档后保存的压缩文件路径-->
<property name="filePattern" value="src/logs/$${date:yyyy-MM}/untitled-%d{yyyy-MM-dd-HH}-%i.log.zip"/>
<!--日志文档超过多大时执行翻转(即:将旧文件压缩,并用新日志文件记录)-->
<property name="maxSize" value="250 MB"/>
<!--记录的日志文件超过多少时,旧文件会被删除-->
<property name="maxFile" value="50"/>
<!--打印日志的时候是否标注日志在项目的位置信息-->
<property name="useLocation" value="true"/>
<!--日志输出格式-->
<property name="layoutPattern" value="%date %p %c{1.} [%thread] %location %message %exception%n"/>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${layoutPattern}"/>
</Console>
<!-- -->
<RollingRandomAccessFile name="File" fileName="${filePath}" filePattern="${filePattern}">
<PatternLayout pattern="${layoutPattern}"/>
<Policies>
<!--基于时间的归档,一般不使用-->
<!--这个6基于filePattern归档文件的时间,本文件归档的filePattern最后的HH表示小时,因此此文件每6个小时进行一次归档-->
<!-- <TimeBasedTriggeringPolicy interval="6" modulate="true"/>-->
<!--基于文件大小的归档-->
<SizeBasedTriggeringPolicy size="${maxSize}"/>
</Policies>
<DefaultRolloverStrategy max="${maxFile}"/>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="OneFile" fileName="${filePath_onlyOne}" filePattern="${filePattern}">
<PatternLayout pattern="${layoutPattern}"/>
<Policies>
<!--基于文件大小的归档-->
<SizeBasedTriggeringPolicy size="${maxSize}"/>
</Policies>
<DefaultRolloverStrategy max="${maxFile}"/>
</RollingRandomAccessFile>
</Appenders>
<!--这个只能自行配置了-->
<Loggers>
<!--异步日志Root(与同步Root之间只能存在一个)-->
<!-- <AsyncRoot level="DEBUG" includeLocation="${useLocation}">-->
<!-- <AppenderRef ref="Console"/>-->
<!-- <AppenderRef ref="File"/>-->
<!-- </AsyncRoot>-->
<!-- 同步Root-->
<Root level="INFO" >
<AppenderRef ref="File"/>
<AppenderRef ref="Console"/>
</Root>
<!--异步日志(与同步日志可同时存在)-->
<!--name属性值请自定义-->
<!-- <AsyncLogger name="AsyncLogger" level="DEBUG" includeLocation="${useLocation}" additivity="false">-->
<!-- <AppenderRef ref="File"/>-->
<!-- </AsyncLogger>-->
<!-- 同步日志-->
<!-- <Logger name="com.memoforward.dao" level="DEBUG" includeLocation="${useLocation}" additivity="false">-->
<!-- <AppenderRef ref="Console"/>-->
<!-- </Logger>-->
</Loggers>
</Configuration>
3.2 配置的简单说明:
3.2.1 RollingRandomAccessFile标签
-
RollingRandomAccessFile的三个属性的含义:
- name:这个Appender的引用名
- fileName:日志文件的路径及名称(文件夹或文件不存在就自动创建)
- filePattern:日志归档后的文件的保存路径和名称(如果加了.zip就自动压缩)
-
RollingRandomAccessFile必须配置的两个字标签:
- TriggerPolicy:文件翻转触发机制。翻转的意思就是旧文件保存(归档),另起新文件进行记录。
在上述配置文件中,其触发机制是SizeBasedTriggeringPolicy,即:当文件大小超过一定阈值后,进行自动保存 - RolloverStrategy:文件翻转策略。
在上述配置文件中,其策略是默认策略,当归档的文件数量达到一定数值后,就自动删除旧文件(如果不配置,则默认为7)
- TriggerPolicy:文件翻转触发机制。翻转的意思就是旧文件保存(归档),另起新文件进行记录。
-
测试代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
public class Log4j2Test {
// private static final Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
private static final Logger logger = LoggerFactory.getLogger("AsyncLogger");
@Test
public void testLog(){
for(int i = 0; i < 500; i++){
logger.error(Integer.toString(i));
}
}
}
- 测试结果如下
设置了保存的文件最大为1KB,最大保存文件数量为10,保存500条日志。每篇日志能保存9条语句。
- untitled.log输出如下:
2019-08-28 19:16:52,248 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 491
2019-08-28 19:16:52,248 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 492
2019-08-28 19:16:52,248 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 493
2019-08-28 19:16:52,248 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 494
2019-08-28 19:16:52,248 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 495
2019-08-28 19:16:52,248 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 496
2019-08-28 19:16:52,249 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 497
2019-08-28 19:16:52,250 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 498
2019-08-28 19:16:52,250 ERROR AsyncLogger [main] Log4j2Test.testLog(Log4j2Test.java:11) 499
· 可见,如果不设置翻转的机制,要么会在一个日志文件内记录所有的数据,要么仅会保存7条归档的日志。一般情况下,希望能把所有的归档日志文件都保存下来,因此RolloverStragety的值要设置的大一点。
3.2.2 Logger的配置
· Logger分为同步和异步,只有一个RootLogger。异步的实现就是我们之前引入的那个com.lmax.disruptor包,当我们启用异步日志记录的时候,就会新建一个Disruptor对象,具体的日志记录流程建议参考这位大佬的文章:Log4j2中的同步日志与异步日志。
-
Logger中属性的含义:
- name:在代码getLogger的名字,root没有此属性。
- level:日志的打印级别为"OFF>FATAL>ERROR>WARN>INFO>DEBUG>TRACE"。logger在记录日志的时候,只会记录level界别及以上的日志内容(OFF永远不会打印)。如设置了ERROR,只会打印FATAL和ERROR。
- includeLoacation:日志打印的时候是否会输出位置(打印位置会降低性能)。
- additivity:root没有,配置此属性值为true来避免重复打印(如果为false,root会重复打印一次相同级别的日志),至于为什么会重复打印,还是建议大家看一下官方文档,这里不赘述了。
-
Logger的子标签
1.< AppenderRef ref=“xxx” > 表示这个logger将用到何种Appender,ref内写Appender的name属性值。
3.2.3 PatternLayout解释
· 本配置文件默认的layoutPattern为:%date %p %c{1.} [%thread] %location %message %exception%n
· 官网有特别特别详细的说明文档,我这个都是对着官网写出来的,特别好理解,强烈建议大家对看文档。传送门:log4j2的LayoutPattern;本博客就只介绍一下上面的这行配置。
格式 | 说明 |
---|---|
%date | 输出日期和时间:yyyy-MM-dd HH:mm:ss,SSS(年-月-日 时:分:秒,毫秒) |
%p | 输出日志的打印级别 |
%c{1.} | 输出该日志所处的缩略类路径 |
%[thread] | 打印执行该日志记录的线程名 |
%location | 打印日志语句在项目代码中的位置 |
%message | 日志内容 |
%exception | 如果出现了异常,则打印异常 |
%n | 类似于/n,是换行符 |
四、总结
· 日志记录一直是一个很尴尬的点,感觉不难,但一直理解不是很深刻,其实我们最常用的就是把日志输出到控制台或者输出到文件,私以为只要把这两个方面搞明白了就行。以后做项目,尽量就用slf4j+log4j2来记录日志啦。