Java日志体系
Java日志体系
1、Java⽇志体系
1.1 体系概述
1.1.1 ⽇志接⼝
- JCL:Apache基⾦会所属的项⽬,是⼀套Java⽇志接⼝,之前叫Jakarta Commons Logging,后更名为Commons Logging,简称JCL
- SLF4J:Simple Logging Facade for Java,缩写Slf4j,是⼀套简易Java⽇志⻔⾯,只提供相关接⼝,和其他⽇志⼯具之间需要桥接
1.1.2 ⽇志实现
- JUL:JDK中的⽇志⼯具,也称为jdklog、jdk-logging,⾃Java1.4以来sun的官⽅提供。
- Log4j:⾪属于Apache基⾦会的⼀套⽇志框架,现已不再维护
- Log4j2:Log4j的升级版本,与Log4j变化很⼤,不兼容
- Logback:⼀个具体的⽇志实现框架,和Slf4j是同⼀个作者,性能很好
1.2 发展历程
1.2.1 上古时代
在JDK 1.3及以前,Java打⽇志依赖System.out.println(), System.err.println()或者
e.printStackTrace(),Debug⽇志被写到STDOUT流,错误⽇志被写到STDERR流。这样打⽇志有⼀个⾮常⼤的缺陷,⾮常机械,⽆法定制,且⽇志粒度不够细分。
代码
System.out.println("123");
System.err.println("456");
1.2.2 开创先驱
于是,Ceki Gülcü 于2001年发布了Log4j,并将其捐献给了Apache软件基⾦会,成为Apache 基⾦会的顶级项⽬。后来衍⽣⽀持C, C++, C#, Perl, Python, Ruby等语⾔。 Log4j 在设计上⾮常优秀,它定义的Logger、Appender、Level等概念对后续的 Java Log 框架有深远的影响,如今的很多⽇志框架基本沿⽤了这种思想。Log4j 的性能是个问题,在Logback 和 Log4j2 出来之后,2015年9⽉,Apache软件基⾦会宣布,Log4j不再维护,建议所有相关项⽬升级到Log4j2
pom:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>apache-log4j-extras</artifactId>
<version>1.2.17</version>
</dependency>
配置:
log4j.rootLogger=info,stdout,dailyfile
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= log4j-console:%c{2} : %p %l%m%n
#dailyfile
log4j.appender.dailyfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyfile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.dailyfile.File=./log4j/dailyfile.log
log4j.appender.dailyfile.Append=true
log4j.appender.dailyfile.Threshold=INFO
log4j.appender.dailyfile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyfile.layout.ConversionPattern=log4j-file:[%d{yyyy-MM-dd HH:mm:ss a}] [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
代码:
import org.apache.log4j.Logger;
Logger logger = Logger.getLogger(Demo.class);
logger.info("xxxx");
1.2.3 搞事情的JUL
sun公司对于log4j的出现内⼼隐隐表示嫉妒。于是在jdk1.4版本后,开始搞事情,增加了⼀个包为java.util.logging,简称为JUL,⽤以对抗log4j ,但是却给开发造成了麻烦。相互引⽤的项⽬之间可能使⽤了不同的⽇志框架,经常将代码搞得⼀⽚混乱。
代码:
import java.util.logging.Logger;
Logger loggger = Logger.getLogger(Demo.class.getName());
logger.finest("xxxx");
配置路径:
$JAVA_HOME/jre/lib/logging.properties
JUL功能远不如log4j完善,⾃带的Handlers有限,性能和可⽤性上也⼀般,JUL在Java1.5以后才有所提升。
1.2.4 JCL应运⽽⽣
从上⾯可以看出,JUL的api与log4j是完全不同的(参数只接受string)。由于⽇志系统互相没有关联,彼此没有约定,不同⼈的代码使⽤不同⽇志,替换和统⼀也就变成了⽐较棘⼿的⼀件事。假如你的应⽤使⽤log4j,然后项⽬引⽤了⼀个其他团队的库,他们使⽤了JUL,你的应⽤就得使⽤两个⽇志系统了,然后其他团队⼜使⽤了simplelog……这个时候如果要调整⽇志的输出级别,⽤于跟踪某个信息,简直就是⼀场灾难。
那这个状况该如何解决呢?答案就是进⾏抽象,抽象出⼀个接⼝层,对每个⽇志实现都适配或者转接,这样这些提供给别⼈的库都直接使⽤抽象层即可 ,以后调⽤的时候,就调⽤这些接⼝。(⾯向接⼝思想)
于是,JCL(Jakarta Commons Logging)应运⽽⽣,也就是commons-logging-xx.jar组件。JCL 只提供 log 接⼝,具体的实现则在运⾏时动态寻找。这样⼀来组件开发者只需要针对 JCL 接⼝开发,⽽调⽤组件的应⽤程序则可以在运⾏时搭配⾃⼰喜好的⽇志实践⼯具。
那接⼝下真实的⽇志是谁呢?参考下图:
JCL会在ClassLoader中进⾏查找,如果能找到Log4j 则默认使⽤log4j 实现,如果没有则使⽤JUL(jdk⾃带的) 实现,再没有则使⽤JCL内部提供的SimpleLog 实现。(代码验证)
pom:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
代码:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Log log =LogFactory.getLog(Demo.class);
log.info('xxx');
JCL缺点也很明显,⼀是效率较低,⼆是容易引发混乱,三是JCL的机制有很⼤的可能会引发内存泄露。
同时,JCL的书写存在⼀个不太优雅的地⽅,典型的场景如下:
假如要输出⼀条debug⽇志,⽽⼀般情况下,⽣产环境 log 级别都会设到 info 或者以上,那这条log 是不会被输出的。于是,在代码⾥就出现了
logger.debug("this is a debug info , message :" + msg);
这个有什么问题呢?虽然⽣产不会打出⽇志,但是这其中都会做⼀个字符串连接操作,然后⽣成⼀个新的字符串。如果这条语句在循环或者被调⽤很多次的函数中,就会多做很多⽆⽤的字符串连接,影响性能。
所以,JCL推荐的写法如下:
if (logger.isDebugEnabled()) {
logger.debug("this is a debug info , message :" + msg);
}
虽然解决了问题,但是这个代码实在看上去不怎么舒服…
1.2.5 再起波澜
于是,针对以上情况,log4j的作者再次出⼿,他觉得JCL不好⽤,⾃⼰⼜写了⼀个新的接⼝api,就是slf4j,并且为了追求更极致的性能,新增了⼀套⽇志的实现,就是logback,⼀时间烽烟⼜起……
坐标:
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!--slf - logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="logPattern" value="logback-xml:[ %-5level] %logger{96} [%line] [%thread]- %msg%n"></property>
<!-- 控制台的标准输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
logback-core 提供基础抽象,logback-classic 提供⽇志实现,并且直接就是基于Slf4j API。所以slf4j配合logback来完成⽇志时,不需要像其他的⽇志框架⼀样提供适配器。
slf4j本身并没有实际的⽇志输出能⼒,它底层还是需要去调⽤具体的⽇志框架API,也就是它需要跟具体的⽇志框架结合使⽤。由于具体⽇志框架⽐较多,⽽且互相也⼤都不兼容,⽇志⻔⾯接⼝要想实现与任意⽇志框架结合就需要额外对应的桥接器。
有了新的slf4j后,上⾯的字符串拼接问题,被以下代码所取代,⽽logback也提供了更⾼级的特性,如异步 logger,Filter等。
logger.debug("this is a debug info , message : {}", msg);
事情结束了吗?没有,log4j的粉丝们并不开⼼,于是下⼀代诞⽣了……
1.2.6 再度⻘春
前⾯提到,log4j由apache宣布,2015年后,不再维护。推荐⼤家升级到log4j2,虽然log4j2沿袭了log4j的思想,然⽽log4j2和log4j完全是两码事,并不兼容。
log4j2以性能著称,它⽐其前身Log4j 1.x提供了重⼤改进,同时类⽐logback,它提供了Logback中可⽤的许多改进,同时修复了Logback架构中的⼀些固有问题。功能上,它有着和Logback相同的基本操作,同时⼜有⾃⼰独特的部分,⽐如:插件式结构、配置⽂件优化、异步⽇志等。
pom:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.1</version>
</dependency>
代码:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger = LogManager.getLogger(Demo.class);
logger.debug("debug Msg");
配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info" monitorInterval="30">
<Properties>
<Property name="pattern">log4j2:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n</Property>
</Properties>
<appenders>
<!--console :控制台输出的配置-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}"/>
</Console>
</appenders>
<loggers>
<logger name="org.springframework" level="INFO"></logger>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
到log4j2,轰轰烈烈的java log战役基本就结束了。下⾯的章节,我们将进⼊⽇志⼯具的详细配置阶段。
1.3 配置讲解
1.3.1 概述
1)⽇志级别
⼀个完整的⽇志组件都要具备⽇志级别的概念,每种⽇志组件级别定义不同,⽇常编码最经常⽤到的主流分级如下(由低到⾼):
- trace:路径跟踪
- debug:⼀般⽤于⽇常调式
- info:打印重要信息
- warn:给出警告
- error:出现错误或问题
每个⽇志组件的具体级别划分稍有不同,参考下⽂各章节。
2)⽇志组件
- appender:⽇志输出⽬的地,负责⽇志的输出 (输出到什么 地⽅)
- logger:⽇志记录器,负责收集处理⽇志记录 (如何处理⽇志)
- layout:⽇志格式化,负责对输出的⽇志格式化(以什么形式展现)
1.3.2 jul
1)配置⽂件:
默认情况下配置⽂件路径为$JAVAHOME\jre\lib\logging.properties
可以指定配置⽂件
static {
System.setProperty("java.util.logging.config.file",
Demo.class.getClassLoader().getResource("logging.properties").getPath());
}
代码实践:配置⽂件位置,⽇志输出级别,如何输出低于INFO级别的信息
2)级别:
- SEVERE(最⾼值)
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST(最低值)
- OFF,关闭⽇志。
- ALL,启⽤所有⽇志。
3)处理器:
- StreamHandler:⽇志记录写⼊OutputStream。
- ConsoleHandler:⽇志记录写⼊System.err。
- FileHandler:⽇志记录写⼊单个⽂件或⼀组滚动⽇志⽂件。
- SocketHandler:⽇志记录写⼊远程TCP端⼝的处理程序。
- MemoryHandler:缓冲内存中⽇志记录。
4)格式化:
- SimpleFormatter:格式化为简短的⽇志记录摘要。
- XMLFormatter:格式化为详细的XML结构信息。
- 可⾃定输出格式,继承抽象类java.util.logging.Formatter即可。
- 代码实践:
- 如何修改输出级别?
1.3.3 log4j
1)配置⽂件:
- 启动时,默认会寻找source folder下的log4j.xml
- 若没有,会寻找log4j.properties
2)级别:
- FATAL(最⾼)
- ERROR
- WARN
- INFO
- DEBUG (最低)
- OFF,关闭⽇志。
- ALL,启⽤所有⽇志。
3)处理器:
- org.apache.log4j.ConsoleAppender(控制台)
- org.apache.log4j.FileAppender(⽂件)
- org.apache.log4j.DailyRollingFileAppender(每天产⽣⼀个⽇志⽂件)
- org.apache.log4j.RollingFileAppender(⽂件⼤⼩到达指定尺⼨的时候产⽣⼀个新的⽂件)
- org.apache.log4j.WriterAppender(将⽇志信息以流格式发送到任意指定的地⽅)
4)格式化:
- org.apache.log4j.HTMLLayout(以HTML表格形式布局)
- org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
- org.apache.log4j.SimpleLayout(包含⽇志信息的级别和信息字符串)
- org.apache.log4j.TTCCLayout(包含⽇志产⽣的时间、线程、类别等等信息)
5) 代码实践:
- Appender:Console,DailyRollingFile
- Layout:Pattern
log4j.rootLogger=info,stdout,dailyfile
#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= log4j-console:%c{2} : %p %l%m%n
#dailyfile
log4j.appender.dailyfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyfile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.dailyfile.File=./log4j/dailyfile.log
log4j.appender.dailyfile.Append=true
log4j.appender.dailyfile.Threshold=INFO
log4j.appender.dailyfile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyfile.layout.ConversionPattern=log4j-file:[%d{yyyy-MM-dd HH:mm:ss a}] [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="true"
xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value=""/>
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="DEBUG"/>
<param name="LevelMax" value="DEBUG"/>
</filter>
</appender>
<appender name="DAILYROLLINGFILE"
class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="log4j.log"/>
<param name="DatePattern" value="yyyy-MM-dd"/>
<param name="Append" value="true"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c
Method: %l ]%n%p:%m%n"/>
</layout>
</appender>
<root>
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DAILYROLLINGFILE"/>
</root>
</log4j:configuration>
1.3.4 logback
http://logback.qos.ch/manual/index.html
1)配置⽂件:
- Logback tries to find a file called logback-test.xml in the classpath.
- If no such file is found, logback tries to find a file called logback.groovy in the classpath.
- If no such file is found, it checks for the file logback.xml in the classpath…
- If no such file is found, service-provider loading facility (introduced in JDK 1.6) is used to resolve the implementation of com.qos.logback.classic.spi.Configurator interface by looking up the file META-INF\services\ch.qos.logback.classic.spi.Configurator in the class path. Its contents should specify the fully qualified class name of the desired Configurator implementation.
- If none of the above succeeds, logback configures itself automatically using the BasicConfigurator which will cause logging output to be directed to the console.
2)级别:
⽇志打印级别 ALL > TRACE > FATAL > DEBUG > INFO > WARN > ERROR > OFF
3)处理器:
http://logback.qos.ch/manual/appenders.html
4)格式化:
http://logback.qos.ch/manual/layouts.html
5) 代码实战:
- Appender:Console,Rollingfile
- Layout:Xml,Pattern,Html , ⾃定义
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="3 seconds" debug="false">
<!-- lOGGER PATTERN 根据个⼈喜好选择匹配 -->
<property name="logPattern" value="logback:[ %-5level] [%date{yyyy-MM-dd
HH:mm:ss.SSS}] %logger{96} [%line] [%thread]- %msg%n"></property>
<!-- 动态⽇志级别 -->
<jmxConfigurator/>
<!-- 控制台的标准输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<!-- 滚动⽂件 -->
<appender name="ROLLING_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>./logback.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logback.log.%d{yyyy-MM-dd}.zip</fileNamePattern>
<!-- 最⼤保存时间 -->
<maxHistory>2</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<!-- DB -->
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource
class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://172.17.0.203:3306/log?useSSL=false</url>
<user>root</user>
<password>root</password>
</connectionSource>
</appender>
<!-- ASYNC_LOG -->
<appender name="ASYNC_LOG" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失⽇志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的⽇志 -
->
<discardingThreshold>0</discardingThreshold>
<!- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>3</queueSize>
<appender-ref ref="STDOUT"/>
</appender>
<!-- ⽇志的记录级别 -->
<!-- 在定义后引⽤APPENDER -->
<root level="DEBUG">
<!-- 控制台 -->
<appender-ref ref="STDOUT"/>
<!-- ROLLING_FILE -->
<appender-ref ref="ROLLING_FILE"/>
<!-- ASYNC_LOG -->
<appender-ref ref="ASYNC_LOG"/>
</root>
</configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<!--<charset>UTF-8</charset>-->
<!--<pattern>${logPattern}</pattern>-->
<!--<layout class="com.itheima.logback.MySampleLayout" />-->
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>%relative%thread%mdc%level%logger%msg</pattern>
</layout>
<!--<layout class="ch.qos.logback.classic.log4j.XMLLayout">-->
<!--<locationInfo>false</locationInfo>-->
<!--</layout>-->
</encoder>
</appender>
1.3.5 jcl
1)配置⽂件:
- ⾸先在classpath下寻找commons-logging.properties⽂件。如果找到,则使⽤其中定义的Log实现类;如果找不到,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使⽤其定义的Log实现类;
- 查看classpath中是否有Log4j的包,如果发现,则⾃动使⽤Log4j作为⽇志实现类;
- 否则,使⽤JDK⾃身的⽇志实现类(JDK1.4以后才有⽇志实现类);
- 否则,使⽤commons-logging⾃⼰提供的⼀个简单的⽇志实现类SimpleLog;
2)级别:
- jcl有5个级别:trace < debug < info < warn < error
3)代码实战:
- ⽇志查找顺序: log4j-jul-slog
- commons-logging.properties配置
#指定⽇志对象:
#org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
#org.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
#指定⽇志⼯⼚:
org.apache.commons.logging.LogFactory =
org.apache.commons.logging.impl.LogFactoryImpl
1.3.6 log4j2
1)配置⽂件:
- Log4j will inspect the log4j.configurationFile system property and,
if set, will attempt to load the configuration using the ConfigurationFactory that matches the file extension. - If no system property is set the YAML ConfigurationFactory will look for log4j2-test.yaml or log4j2-test.yml in the classpath.
- If no such file is found the JSON ConfigurationFactory will look for log4j2-test.json or log4j2-test.jsn in the classpath.
- If no such file is found the XML ConfigurationFactory will look for log4j2-test.xml in the classpath.
- If a test file cannot be located the YAML ConfigurationFactory will look for log4j2.yaml or log4j2.yml on the classpath.
- If a YAML file cannot be located the JSON ConfigurationFactory will look for log4j2.json or log4j2.jsn on the classpath.
- If a JSON file cannot be located the XML ConfigurationFactory will try to locate log4j2.xml on the classpath.
- If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.
2)级别:
- 从低到⾼为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
3)处理器:
http://logging.apache.org/log4j/2.x/manual/appenders.html
- FileAppender 普通地输出到本地⽂件
- KafkaAppender 输出到kafka队列
- FlumeAppender 将⼏个不同源的⽇志汇集、集中到⼀处。
- JMSQueueAppender,JMSTopicAppender 与JMS相关的⽇志输出
- RewriteAppender 对⽇志事件进⾏掩码或注⼊信息
- RollingFileAppender 对⽇志⽂件进⾏封存(详细)
- RoutingAppender 在输出地之间进⾏筛选路由
- SMTPAppender 将LogEvent发送到指定邮件列表
- SocketAppender 将LogEvent以普通格式发送到远程主机
- SyslogAppender 将LogEvent以RFC 5424格式发送到远程主机
- AsynchAppender 将⼀个LogEvent异步地写⼊多个不同输出地
- ConsoleAppender 将LogEvent输出到命令⾏
- FailoverAppender 维护⼀个队列,系统将尝试向队列中的Appender依次输出LogEvent,直到有⼀个成功为⽌
4)格式化:
http://logging.apache.org/log4j/2.x/manual/layouts.html
5)代码实战:
<?xml version="1.0" encoding="UTF-8"?>
<!--⽇志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE >
ALL -->
<!--status="WARN" :⽤于设置log4j2⾃身内部⽇志的信息输出级别,默认是OFF-->
<!--monitorInterval="30" :间隔秒数,⾃动检测配置⽂件的变更和重新配置本身-->
<configuration status="info" monitorInterval="30">
<Properties>
<!--⾃定义⼀些常量,之后使⽤${变量名}引⽤-->
<Property name="pattern">log4j2:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t]
%c{1}:%L - %msg%n</Property>
</Properties>
<!--appenders:定义输出内容,输出格式,输出⽅式,⽇志保存策略等,常⽤其下三种标签
[console,File,RollingFile]-->
<appenders>
<!--console :控制台输出的配置-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}"/>
</Console>
<!--File :同步输出⽇志到本地⽂件-->
<!--append="false" :根据其下⽇志策略,每次清空⽂件重新输⼊⽇志,可⽤于测试-->
<File name="File" fileName="./log4j2-file.log" append="false">
<PatternLayout pattern="${pattern}"/>
</File>
<RollingFile name="RollingFile" fileName="./log4j2-rollingfile.log"
filePattern="./$${date:yyyy-MM}/log4j2-%d{yyyy-MM-dd}-
%i.log">
<!--ThresholdFilter :⽇志输出过滤-->
<!--level="info" :⽇志级别,onMatch="ACCEPT" :级别在info之上则接
受,onMismatch="DENY" :级别在info之下则拒绝-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${pattern}"/>
<!-- Policies :⽇志滚动策略-->
<Policies>
<!-- TimeBasedTriggeringPolicy :时间滚动策略,
默认0点产⽣新的⽂件,
interval="6" : ⾃定义⽂件滚动时间间隔,每隔6⼩时产⽣新⽂件,
modulate="true" : 产⽣⽂件是否以0点偏移时间,即6点,12点,18点,0点-->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<!-- SizeBasedTriggeringPolicy :⽂件⼤⼩滚动策略-->
<SizeBasedTriggeringPolicy size="1 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同⼀⽂件夹下7个⽂
件,这⾥设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引⼊的appender,appender才会⽣效-->
<loggers>
<!--过滤掉spring和mybatis的⼀些⽆⽤的DEBUG信息-->
<!--Logger节点⽤来单独指定⽇志的形式,name为包路径,⽐如要为org.springframework
包下所有⽇志指定为INFO级别等。 -->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<!--AsyncLogger :异步⽇志,LOG4J有三种⽇志模式,全异步⽇志,混合模式,同步⽇志,性能
从⾼到底,线程越多效率越⾼,也可以避免⽇志卡死线程情况发⽣-->
<!--additivity="false" : additivity设置事件是否在root logger输出,为了避免重
复输出,可以在Logger 标签下设置additivity为”false”-->
<AsyncLogger name="AsyncLogger" level="trace" includeLocation="true"
additivity="true">
<appender-ref ref="Console"/>
</AsyncLogger>
<logger name="Kafka" additivity="false" level="debug">
<appender-ref ref="Kafka"/>
<appender-ref ref="Console"/>
</logger>
<!-- Root节点⽤来指定项⽬的根⽇志,如果没有单独指定Logger,那么就会默认使⽤该Root
⽇志输出 -->
<root level="info">
<appender-ref ref="Console"/>
<!--<appender-ref ref="File"/>-->
<!--<appender-ref ref="RollingFile"/>-->
<!--<appender-ref ref="Kafka"/>-->
</root>
</loggers>
</configuration>
1.3.7 slf4j
1)配置⽂件:
具体⽇志输出内容取决于⽣效的具体⽇志实现
2)级别:
slf4j⽇志级别有五种:ERROR、WARN、INFO、DEBUG、TRACE,级别从⾼到底
3)slf⽇志实现:
<!--slf - jcl-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.30</version>
</dependency>
<!-- slf - log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
<!-- slf4j - log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.13.0</version>
</dependency>
<!--slf - jul-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.30</version>
</dependency>
<!--slf - simplelog-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
问题:多个桥接器的话,slf4j怎么处理呢?
使⽤在class path中较早出现的那个,如在maven中,会使⽤在pom.xml中定义较靠前的桥接器(代码验证)
⼩知识:
桥接器会传递依赖到对应的下游⽇志组件,⽐如slf4j-log4j12会附带log4j的jar包依赖(代码验证)
4)其他⽇志转slf
<!--其他⽇志转slf-->
<!--jul - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!--jcl - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!--log4j - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!--log4j2 - slf-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.13.0</version>
</dependency>
5)slf4j⽇志环
<!-- 演示实例:slf4j - log4j - slf4j -->
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>apache-log4j-extras</artifactId>
<version>1.2.17</version>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<!--log4j - slf-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
<!-- slf - log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
依赖展示:
报错结果:
SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the
class path, preempting StackOverflowError.
SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more
details.
java.lang.ExceptionInInitializerError
at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:72)
at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:45)
at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
at com.itheima.slf4j.Demo.<clinit>(Demo.java:8)
Caused by: java.lang.IllegalStateException: Detected both log4j-over-slf4j.jar
AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError.
See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.
at org.slf4j.impl.Log4jLoggerFactory.<clinit>(Log4jLoggerFactory.java:54)
... 8 more
Exception in thread "main"
Process finished with exit code 1
经验:使⽤slf桥接和绑定的时候要留⼼,不要⾃相⽭盾形成环
1.4⽇志建议
1.4.1 ⻔⾯约束
使⽤⻔⾯,⽽不是具体实现
使⽤ Log Facade 可以⽅便的切换具体的⽇志实现。⽽且,如果依赖多个项⽬,使⽤了不同的LogFacade,还可以⽅便的通过 Adapter 转接到同⼀个实现上。如果依赖项⽬直接使⽤了多个不同的⽇志实现,会⾮常糟糕。
经验之谈:⽇志⻔⾯,⼀般现在推荐使⽤ Log4j-API 或者 SLF4j,不推荐继续使⽤ JCL。
1.4.2 单⼀原则
只添加⼀个⽇志实现
项⽬中应该只使⽤⼀个具体的 Log Implementation,如果在依赖的项⽬中,使⽤的 Log Facade不⽀持当前 Log Implementation,就添加合适的桥接器。
经验之谈:jul性能⼀般,log4j性能也有问题⽽且不再维护,建议使⽤ Logback 或者Log4j2。
1.4.3 依赖约束
⽇志实现坐标应该设置为optional并使⽤runtime scope
在项⽬中,Log Implementation的依赖强烈建议设置为runtime scope,并且设置为optional。例如项⽬中使⽤了 SLF4J 作为 Log Facade,然后想使⽤ Log4j2 作为 Implementation,那么使⽤ maven添加依赖的时候这样设置:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
设为optional,依赖不会传递,这样如果你是个lib项⽬,然后别的项⽬使⽤了你这个lib,不会被引⼊不想要的Log Implementation 依赖;
Scope设置为runtime,是为了防⽌开发⼈员在项⽬中直接使⽤Log Implementation中的类,强制约束开发⼈员使⽤Facade接⼝。
1.4.4 避免传递
尽量⽤exclusion排除依赖的第三⽅库中的⽇志坐标
同上⼀个话题,第三⽅库的开发者却未必会把具体的⽇志实现或者桥接器的依赖设置为optional,然后你的项⽬就会被迫传递引⼊这些依赖,⽽这些⽇志实现未必是你想要的,⽐如他依赖了Log4j,你想使⽤Logback,这时就很尴尬。另外,如果不同的第三⽅依赖使⽤了不同的桥接器和Log实现,极有可能会形成环
这种情况下,推荐的处理⽅法,是使⽤exclude来排除所有的这些Log实现和桥接器的依赖,只保留第三⽅库⾥⾯对Log Facade的依赖。
实例:依赖jstorm会引⼊Logback和log4j-over-slf4j,如果你在⾃⼰的项⽬中使⽤Log4j或其他Log实现的话,就需要加上exclusion:
<dependency>
<groupId>com.alibaba.jstorm</groupId>
<artifactId>jstorm-core</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
1.4.5 注意写法
避免为不会输出的log买单
Log库都可以灵活的设置输出级别,所以每⼀条程序中的log,都是有可能不会被输出的。这时候要注意不要额外的付出代价。实例如下:
logger.debug("this is debug: " + message);
logger.debug("this is json msg: {}", toJson(message));
前⾯讲到,第⼀条的字符串拼接,即使⽇志级别⾼于debug不会打印,依然会做字符串连接操作;
第⼆条虽然⽤了SLF4J/Log4j2 中的懒求值⽅式,但是toJson()这个函数却是总会被调⽤并且开销更⼤。
推荐的写法如下:
// SLF4J/LOG4J2
logger.debug("this is debug:{}", message);
// LOG4J2
logger.debug("this is json msg: {}", () -> toJson(message));
// SLF4J/LOG4J2
if (logger.isDebugEnabled()) {
logger.debug("this is debug: " + message);
}
1.4.6 减少分析
输出的⽇志中尽量不要使⽤⾏号,函数名等信息
原因是,为了获取语句所在的函数名,或者⾏号,log库的实现都是获取当前的stacktrace,然后分
析取出这些信息,⽽获取stacktrace的代价是很昂贵的。如果有很多的⽇志输出,就会占⽤⼤量的
CPU。在没有特殊需要的情况下,建议不要在⽇志中输出这些这些字段。
1.4.7 精简⾄上
log中尽量不要输出稀奇古怪的字符,这是个习惯和约束问题。有的同学习惯⽤这种语句:
logger.debug("=====================================:{}",message);
输出了⼤量⽆关字符,虽然⾃⼰⼀时痛快,但是如果所有⼈都这样做的话,那log输出就没法看了!正确的做法是⽇志只输出必要信息,如果要过滤,后期使⽤grep来筛选,只查⾃⼰关⼼的⽇志。