Slf4j&logback&log4j2之间的关系,以及slf4j&log4j的使用
[TOC]
用了几年的Java日志框架,但却对里面的逻辑关系不是特别清楚,准备花时间理清一下其中的关系以及基本的使用说明
Log4j 是 Apache 的一个 Java 的日志库,通过使用 Log4j,我们可以控制日志信息输送的目的地(控制台、文件、数据库等);我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
Logback,一个 “可靠、通用、快速而又灵活的 Java 日志框架”。logback 当前分成三个模块:logback-core,logback- classic 和 logback-access。logback-core 是其它两个模块的基础模块。logback-classic 是 log4j 的一个改良版本。此外 logback-classic 完整实现 SLF4J API 使你可以很方便地更换成其它日志系统,如 log4j 或 JDK14 Logging。logback-access 模块与 Servlet 容器(如 Tomcat 和 Jetty)集成,以提供 HTTP 访问日志功能。请注意,您可以在 logback-core 之上轻松构建自己的模块。
Apache Log4j 2 是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了重大改进,并提供了 Logback 中可用的许多改进,同时修复了 Logback 架构中的一些问题。
现在最优秀的 Java 日志框架是 Log4j2,没有之一。根据官方的测试表明,在多线程环境下,Log4j2 的异步日志表现更加优秀。在异步日志中,Log4j2 使用独立的线程去执行 I/O 操作,可以极大地提升应用程序的性能。
在官方的测试中,Log4j1/Logback/Log4j2 三个日志框架的异步日志性能比较如下图所示。
其中,Loggers all async
是基于 LMAX Disruptor 实现的。可见 Log4j2 的异步日志性能是最棒的。
log4j,log4j2,logback 异步日志性能比较
下图比较了 Log4j2 框架Sync
、Async Appenders
和Loggers all async
三者的性能。其中Loggers all async
表现最为出色,而且线程数越多,Loggers all async
性能越好。
log4j2 同步异步 Appender 比较
上述介绍的是一些日志框架的实现(Log4j、Logback、log4j2),他们都有各自的API可以调用,但是我们更多是使用通用的日志调用接口来解决系统与日志实现框架的耦合性。日志通用接口,它不是一个真正的日志实现,而是一个抽象层( abstraction layer),它允许你在后台使用任意一个日志实现。常见的通用日志接口有commons logging
、slf4j
,由于前面一个基本没有使用过,所以不过多进行介绍。
Apache Log4j 2 是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了重大改进,并提供了 Logback 中可用的许多改进,同时修复了 Logback 架构中的一些问题。所以后面的例子环境为slf4j&log4j2
// log4j核心包
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
// 除了核心包意外,还需要将log4j2与slf4j建立连接
implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.14.1'
// 最后引入Slf4j的API
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.31'
根节点,有以下两个属性
属性。使用来定义常量,以便在其他配置项中引用,该配置是可选的,例如定义日志的存放位置
输出源,用于定义日志输出的地方。 log4j2 支持的输出源有很多,有控制台 ConsoleAppender、文件 FileAppender、AsyncAppender、RandomAccessFileAppender、RollingFileAppender、RollingRandomAccessFile 等
ByteBuffer + RandomAccessFile
而不是 BufferedOutputStream
。与 FileAppender 相比,我们在测量中看到 “bufferedIO = true”,性能提升了 20-200% 。ByteBuffer + RandomAccessFile
而不是BufferedOutputStream
。与 RollingFileAppender 相比,我们在测量中看到 “bufferedIO = true”,性能提升了 20-200%。RollingRandomAccessFileAppender 写入 fileName 参数中指定的文件,并根据 TriggeringPolicy 和 RolloverPolicy 滚动文件。Filters 决定日志事件能否被输出。过滤条件有三个值:ACCEPT(接受)
,DENY(拒绝)
,NEUTRAL(中立)
。
控制台或文件输出源(Console、File、RollingRandomAccessFile)都必须包含一个 PatternLayout 节点,用于指定输出文件的格式(如 日志输出的时间 文件 方法 行数 等格式)。
简单示例:
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%n" charset="UTF-8"/>
详细配置请查看官网
TimeBasedTriggeringPolicy
基于时间的触发策略。该策略主要是完成周期性的 log 文件封存工作。有两个参数:
interval,integer 型,指定两次封存动作之间的时间间隔。这个配置需要和filePattern
结合使用,filePattern
日期格式精确到哪一位,interval 也精确到哪一个单位。注意filePattern
中配置的文件重命名规则是 %d{yyyy-MM-dd HH-mm-ss}-%i,最小的时间粒度是 ss,即秒钟。 TimeBasedTriggeringPolicy 默认的 size 是 1,结合起来就是每 1 秒钟生成一个新文件。如果改成 %d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件
modulate,boolean 型,说明是否对封存时间进行调制。若 modulate=true, 则封存时间将以 0 点为边界进行偏移计算。比如,modulate=true,interval=4hours, 那么假设上次封存日志的时间为 03:00,则下次封存日志的时间为 04:00, 之后的封存时间依次为 08:00,12:00,16:00
简单示例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<RollingRandomAccessFile name="File" fileName="./log/app.log"
filePattern="./log/app-%d{yyyy-MM-dd HH-mm}-%i.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%n" charset="UTF-8"/>
<Policies>
<!-- 每 5s 翻滚一次 -->
<!--<CronTriggeringPolicy schedule="0/5 * * * * ?"/>-->
<!--根据当前filePattern配置,日志文件每3分钟滚动一次-->
<TimeBasedTriggeringPolicy interval="3"/>
<!--日志文件大于10 KB滚动一次-->
<SizeBasedTriggeringPolicy size="10 KB"/>
</Policies>
<!--保存日志文件个数-->
<DefaultRolloverStrategy max="10"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
这两个Strategy
都是控制如何进行日志滚动的,平时大部分用DefaultRolloverStrategy
就可以了。
Loggers 节点,常见的有两种:Root 和 Logger。 Root
节点用来指定项目的根日志,如果没有单独指定Logger
,那么就会默认使用该 Root 日志输出
每个配置都必须有一个根记录器 Root。如果未配置,则将使用默认根 LoggerConfig,其级别为 ERROR 且附加了 Console appender。根记录器和其他记录器之间的主要区别是:1. 根记录器没有 name 属性。2. 根记录器不支持 additivity 属性,因为它没有父级。
Logger 节点用来单独指定日志的形式,比如要为指定包下的 class 指定不同的日志级别等。
使用Logger
元素必须有一个 name 属性,root logger 不用 name 元属性 每个Logger
可以使用 TRACE,DEBUG,INFO,WARN,ERROR,ALL 或 OFF 之一配置级别。如果未指定级别,则默认为 ERROR。可以为 additivity 属性分配值 true 或 false。如果省略该属性,则将使用默认值 true。
Logger
还可以配置一个或多个 AppenderRef 属性。引用的每个 appender 将与指定的Logger
关联。如果在Logger
上配置了多个 appender,则在处理日志记录事件时会调用每个 appender。
默认情况下,Log4j2 在 classpath 下查找名为log4j2.xml
的配置文件。你也可以使用 Java 启动命令指定配置文件的全路径。-Dlog4j.configurationFile=opt/demo/log4j2.xml
,你还可以使用 Java 代码指定配置文件路径
常用日志配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<!-- 日志输出级别 -->
<Property name="LOG_INFO_LEVEL" value="info"/>
<!-- error级别日志 -->
<Property name="LOG_ERROR_LEVEL" value="error"/>
<!-- 在当前目录下创建名为log目录做日志存放的目录 -->
<Property name="LOG_HOME" value="./log"/>
<!-- 档案日志存放目录 -->
<Property name="LOG_ARCHIVE" value="./log/archive"/>
<!-- 模块名称, 影响日志配置名,日志文件名,根据自己项目进行配置 -->
<Property name="LOG_MODULE_NAME" value="spring-boot"/>
<!-- 日志文件大小,超过这个大小将被压缩 -->
<Property name="LOG_MAX_SIZE" value="100 MB"/>
<!-- 保留多少天以内的日志 -->
<Property name="LOG_DAYS" value="15"/>
<!--输出日志的格式:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度, %msg:日志消息,%n是换行符 -->
<Property name="LOG_PATTERN" value="%d [%t] %-5level %logger{0} - %msg%n"/>
<!--interval属性用来指定多久滚动一次-->
<Property name="TIME_BASED_INTERVAL" value="1"/>
</Properties>
<Appenders>
<!-- 控制台输出 -->
<Console name="STDOUT" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<!-- 这个会打印出所有的info级别以上,error级别一下的日志,每次大小超过size或者满足TimeBasedTriggeringPolicy,则日志会自动存入按年月日建立的文件夹下面并进行压缩,作为存档-->
<RollingRandomAccessFile name="RollingRandomAccessFileInfo"
fileName="${LOG_HOME}/${LOG_MODULE_NAME}-infoLog.log"
filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-infoLog-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--如果是error级别拒绝,设置 onMismatch="NEUTRAL" 可以让日志经过后续的过滤器-->
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="DENY" onMismatch="NEUTRAL"/>
<!--如果是info\warn输出-->
<ThresholdFilter level="${LOG_INFO_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,根据当前filePattern设置是1天滚动一次-->
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认同一文件夹下最多保存7个文件-->
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>
<!--只记录error级别以上的日志,与info级别的日志分不同的文件保存-->
<RollingRandomAccessFile name="RollingRandomAccessFileError"
fileName="${LOG_HOME}/${LOG_MODULE_NAME}-errorLog.log"
filePattern="${LOG_ARCHIVE}/${LOG_MODULE_NAME}-errorLog-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="${LOG_ERROR_LEVEL}" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="${TIME_BASED_INTERVAL}"/>
<SizeBasedTriggeringPolicy size="${LOG_MAX_SIZE}"/>
</Policies>
<DefaultRolloverStrategy max="${LOG_DAYS}"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- 开发环境使用 -->
<!--<Root level="${LOG_INFO_LEVEL}">
<AppenderRef ref="STDOUT"/>
</Root>-->
<!-- 测试,生产环境使用 -->
<Root level="${LOG_INFO_LEVEL}">
<AppenderRef ref="RollingRandomAccessFileInfo"/>
<AppenderRef ref="RollingRandomAccessFileError"/>
</Root>
</Loggers>
</Configuration>
如果Root
中的日志包含了Logger
中的日志信息,并且AppenderRef
是一样的配置,则日志会打印两次。
这是 log4j2 继承机制问题,在 Log4j2 中,logger 是有继承关系的,root 是根节点,在 log4j2 中,有个 additivity 的属性,它是子 Logger 是否继承 父 Logger 的 输出源(appender) 的属性。具体说,默认情况下子 Logger 会继承父 Logger 的 appender,也就是说子 Logger 会在父 Logger 的 appender 里输出。若是 additivity 设为 false,则子 Logger 只会在自己的 appender 里输出,而不会在父 Logger 的 appender 里输出。
要打破这种传递性,也非常简单,在 logger 中添加 additivity = “false”,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5level %logger{0} - %msg%n</Pattern>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<!-- name属性为项目包名或者类名 -->
<Logger name="com.jourwon" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
lombok 就是一个注解工具 jar 包,能帮助我们省略一繁杂的代码。
引入依赖
// lombok
compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
testCompileOnly 'org.projectlombok:lombok:1.18.20'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
使用 Lombok 后,@Slf4j 注解生成了 log 日志常量,无需去声明一个 log 就可以在类中使用 log 记录日志。
@Slf4j
public class Log4jTest {
public static void main(String[] args) {
log.error("Something else is wrong here");
}
}