日志类库
Java日志库是最能体现Java库在进化中的渊源关系的,在理解时重点理解日志框架本身和日志门面,以及比较好的实践等。
要关注其历史渊源和设计(比如桥接),而具体在使用时查询接口即可,否则会陷入JUL(JavaUtil Log),JCL(Commons Logging),Log4j,SLF4J, Logback,Log4j2傻傻分不清楚的境地。
1.1 日志系统
1.1.1 JUL(java.util.logging)
JDK1.4开始,通过java.util.logging
提供的日志功能。虽然是官方自带的log lib,JUL的使用不广泛主要原因是:
JUL从JDK1.4才开始加入,当时各种第三方log lib已经被广泛使用了
JUL早期存在性能问题,到JDK1.5上有了不错的进步,但是现在和Logbak/Log4j2相比还是有所不如
JUL的功能不如Logback/Log4j2等完善,比如Output Handler就没有Logback/Log4j2的丰富,有时候需要自己来继承定制,又比如默认没有从ClassPath里加载配置文件的功能
1.1.2 Log4j
Log4j 是 apache
的一个开源项目,创始人 Ceki Gulcu,Log4j
是高度可配置的,并可通过在运行时的外部文件配置。它根据记录的优先级别,并提供机制,以指示记录信息到许多的目的地,诸如:数据库,文件,控制台,UNIX 系统日志等。
Log4j中主要有三个主要组成部分:
loggers
:负责捕获记录信息appenders
:负责发布日志信息layouts
:负责格式化不同风格的日志信息
官网地址:http://logging.apache.org/log4j/2.x/
Log4j的短板在于性能,在Logback和Log4j2出来之后,Log4j的使用也减少了。
1.1.3 Logback
Logback是由Log4j创始人Ceki Gulcu
设计的又一个开源日志组件,是作为Log4j的继承者来开发的,提供了性能更好的实现,异步logger,Filter等更多的特性。
Logback当前分成三个模块:
logback-core
:是其他两个模块的基础模块logback-claassic
:是log4j的一个改良版本,完整实现SLF4J API可以很方便的更换成其他日志系统,如Log4j 或JDK1.4 logginglogbak-access
:访问模块与Servlet容器集成提供通过Http来访问日志的功能
1.1.4 Log4j2
维护Log4j的人为了性能又搞出了Log4j2.
Log4j2和Log4j1.x并不兼容,设计上很大程度上模仿了SLF4J/Logback,性能上也获得很大的提升
Log4j2也做了 Facade/Implementation分离的设计,分成了log4j-api 和 log4j-core.
官网地址:http://logging.apache.org/log4j/2.x/
1.1.5 Log4j vs Logback vs Log4j2
性能上Log4J2要强,但从生态上Logback+SLF4J优先
logback和log4j2都宣称自己是log4j的后代,一个是出于同一个作者,另一个则是在名字上根正苗红。
比较一下Log4j2和logback
log4j2比logback更新:log4j2的GA版在2014年底才推出,比logback晚了好几年,这期间log4j2确实吸收了slf4j和logback的一些优点(比如日志模板),同时应用了不少的新技术
由于采用了更先进的锁机制和LMAX Disruptor库,log4j2的性能优于logback,特别是在多线程环境下和使用异步日志的环境下
二者都支持Filter(应该说是log4j2借鉴了logback的Filter),能够实现灵活的日志记录规则(例如仅对一部分用户记录debug级别的日志)
二者都支持对配置文件的动态更新二者都能够适配slf4j,logback与slf4j的适配应该会更好一些,毕竟省掉了一层适配库
logback能够自动压缩/删除旧日志
logback提供了对日志的HTTP访问功能
log4j2实现了“无垃圾”和“低垃圾”模式。简单地说,log4j2在记录日志时,能够重用对象(如String等),尽可能避免实例化新的临时对象,减少因日志记录产生的垃圾对象,减少垃圾回收带来的性能下降
log4j2和logback各有长处,总体来说,如果对性能要求比较高的话,log4j2相对还是较优的选择。
性能对比
log4j2与logback性能对比的benchmark,这份benchmark是Apache Logging出的,仅供参考。
同步写文件日志的benchmark:
异步写日志的benchmark
当然,这些benchmark都是在日志Pattern中不包含Location信息(如日志代码行号 ,调用者信息,Class名/源码文件名等)时测定的
1.2 日志门面
1.2.1 common-logging
common-logging 是apache的一个开源项目。也称Jakarta Commons Logging(JCL)
common-logging的功能是提供日志功能的API接口,本身并不提供日志的具体实现(除了内部有一个Simple logger的简单实现),而是在运行时动态的绑定日志组件来实现(如log4j、java.util.logging)。
官网地址:https://commons.apache.org/proper/commons-logging/
1.2.2 Sl4j
全称为 Simple Logging Facade for java 即Java简单日志门面
类似于Common-Logging, Slf4j是对不同日志框架提供的一个API封装,可以在部署的时候不修改任何撇脂即可介入一种日志实现方案。但是
Slf4j在编译时静态绑定真正的Log库,使用Slf4j时,如果需要使用某一种日志实现,那么你必须要选择正确的Slf4j的jar包的集合
官网地址: http://www.slf4j.org/
1.2.3 common-logging VS slf4j
slf4j类似与Apache Common-Logging,但是,它在编译时静态绑定真正的日志哭
slf4j一大亮点是:提供了更方便的日志记录方式:
不需要使用Logger.isDebugEnabled()来解决日志因为字符拼接产生的性能问题,Slf4j的方式是使用{}作为字符串替换符,形式如下:
1 | logger.debug("id:{}, name:{}", id, name); |
添加Logback日志
2.1 配置时考虑点
- 支持日志路径,日志level等配置
- 日志控制配置通过application.yml下发
- 按天生成日志,当天的日志> 50M 回滚
- 最多保存10天日志
- 生成的日志中Pattern自定义
- Pattern中添加用户自定义的MDC字段,比如用户信息、request信息。这种方式可以通过AOP切面控制,在MDC中添加requestID,在spring-logback.xml中配置Pattern
- 根据不同的运行环境设置Profile -dev,test,product
- 对控制台,Err和全量日志分别配置
2.2 范例
application.yml
1
2
3
4
5
6
7
8
9
10logging:
level:
root: debug
path: C:/data/logs/springboot-logback-demo
server:
port: 8080
spring:
application:
name: springboot-logback-demo
debug: falsespring-logback.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<configuration>
<!-- 日志根目录-->
<springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="/data/logs/springboot-logback-demo"/>
<!-- 日志级别 -->
<springProperty scope="context" name="LOG_ROOT_LEVEL" source="logging.level.root" defaultValue="DEBUG"/>
<!-- 标识这个"STDOUT" 将会添加到这个logger -->
<springProperty scope="context" name="STDOUT" source="log.stdout" defaultValue="STDOUT"/>
<!-- 日志文件名称-->
<property name="LOG_PREFIX" value="spring-boot-logback" />
<!-- 日志文件编码-->
<property name="LOG_CHARSET" value="UTF-8" />
<!-- 日志文件路径+日期-->
<property name="LOG_DIR" value="${LOG_HOME}/%d{yyyyMMdd}" />
<!--对日志进行格式化-->
<property name="LOG_MSG" value="- | [%X{requestUUID}] | [%d{yyyyMMdd HH:mm:ss.SSS}] | [%level] | [${HOSTNAME}] | [%thread] | [%logger{36}] | --> %msg|%n "/>
<!--文件大小,默认10MB-->
<property name="MAX_FILE_SIZE" value="50MB" />
<!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
<property name="MAX_HISTORY" value="10"/>
<!--输出到控制台-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 输出的日志内容格式化-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${LOG_MSG}</pattern>
</layout>
</appender>
<!--输出到文件-->
<appender name="0" class="ch.qos.logback.core.rolling.RollingFileAppender">
</appender>
<!-- 定义 ALL 日志的输出方式:-->
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件路径,日志文件名称-->
<File>${LOG_HOME}/all_${LOG_PREFIX}.log</File>
<!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件路径,新的 ALL 日志文件名称,“ i ” 是个变量 -->
<FileNamePattern>${LOG_DIR}/all_${LOG_PREFIX}%i.log</FileNamePattern>
<!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
<MaxHistory>${MAX_HISTORY}</MaxHistory>
<!--当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 输出的日志内容格式化-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${LOG_MSG}</pattern>
</layout>
</appender>
<!-- 定义 ERROR 日志的输出方式:-->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 下面为配置只输出error级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<!--日志文件路径,日志文件名称-->
<File>${LOG_HOME}/err_${LOG_PREFIX}.log</File>
<!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件路径,新的 ERR 日志文件名称,“ i ” 是个变量 -->
<FileNamePattern>${LOG_DIR}/err_${LOG_PREFIX}%i.log</FileNamePattern>
<!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
<MaxHistory>${MAX_HISTORY}</MaxHistory>
<!--当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 输出的日志内容格式化-->
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>${LOG_MSG}</Pattern>
</layout>
</appender>
<!-- additivity 设为false,则logger内容不附加至root ,配置以配置包下的所有类的日志的打印,级别是 ERROR-->
<logger name="org.springframework" level="ERROR" />
<logger name="org.apache.commons" level="ERROR" />
<logger name="org.apache.zookeeper" level="ERROR" />
<logger name="com.alibaba.dubbo.monitor" level="ERROR"/>
<logger name="com.alibaba.dubbo.remoting" level="ERROR" />
<!-- ${LOG_ROOT_LEVEL} 日志级别 -->
<root level="${LOG_ROOT_LEVEL}">
<!-- 标识这个"${STDOUT}"将会添加到这个logger -->
<appender-ref ref="${STDOUT}"/>
<!-- FILE_ALL 日志输出添加到 logger -->
<appender-ref ref="FILE_ALL"/>
<!-- FILE_ERROR 日志输出添加到 logger -->
<appender-ref ref="FILE_ERROR"/>
</root>
</configuration>