日志框架 | 日志门面 |
---|---|
jul | JCL |
log4j | SLF4J |
log4j | |
logback |
# 一、日志框架
# 1、JUL
sun java.util.logging
jdk官方推出的日志框架
import java.util.logging.Logger;
Logger loggger = Logger.getLogger(Test.class.getName());
logger.finest("finest");
2
3
4
# 2、log4j
apache log4j
Log4j
被Apache基金会收购,成为Apache其一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件等。
import org.apache.log4j.Logger;
Logger logger = Logger.getLogger(Test.class);
logger.trace("trace");
2
3
4
# 3、log4j2
apache log4j 2
log4j 2
是apache的团队在log4j 的基础上进行完善扩展。(与log4j不是同一个作者)
# 4、logback
Logback
是由log4j创始人设计的又一个开源日志组件。
logback当前分成三个模块:logback-core,logback- classic和logback-access。
logback-core是其它两个模块的基础模块。
logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API (opens new window)使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。
logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。
# 5、jboss-loggging
# 二、日志门面
日志门面,不实现日志功能,用来整合日志。通过日志门面,可以很便捷的将不同的日志实现,转换为指定的统一日志实现。
这样做的好处是,组件开发人员只需针对日志门面(日志接口)开发,而具体的日志实现,由调用组件的应用程序去指定。
# 1、JCL
sun jakarta commons logging
JCL
由jdk提供的日志门面,其只提供log接口,具体的日志实现,则在运行时动态寻找。
JCL是自己去找(ClassLoader),先找到哪个运行哪个。
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Log log =LogFactory.getLog(Test.class);
log.trace('trace');
2
3
4
5
说JCL可能大家有点陌生,讲commons-logging-xx.jar组件,大家总有印象吧。
jcl默认的配置:
如果能找到Log4j则默认使用log4j实现,如果没有则使用jul(jdk自带的)实现,再没有则使用jcl内部提供的 SimpleLog 实现。
JCL动态查找机制进行日志实例化,执行顺序为:commons-logging.properties--->系统环境变量--->log4j--->jul ------>simplelog--->nooplog
# 2、Slf4j
SLF4J
是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现。
slf4j跟JCL机制不一样,其本身不去找,而是由提供桥接器
、适配器
。
- 桥接器:不同的日志实现有其对应的桥接器,提供那个(引入那个包),就运行那个,从而实现具体的日志实现。
- 适配器:由JCL门面,转换至slf4j。
如图所示,应用调了sl4j-api,即日志门面接口。日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体 的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。
由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合,可能需要对应的桥接器,上图红框中的组件即是对应的各种桥接器!
我们在代码中需要写日志,变成下面这么写:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("info");
2
3
4
5
在代码中,并不会出现具体日志框架的api。程序根据classpath中的桥接器类型,和日志框架类型,判断出logger.info应该以什么框架输出!
注意了,如果classpath中不小心引了两个桥接器,那会直接报错的!
因此,在阿里的开发手册上才有这么一条 :
强制:应用中不可直接使用日志系统(log4j、logback)中的 API ,而应依赖使用日志框架 SLF4J 中的 API 。使用门面模式的日志框架,有 利于维护和各个类的日志处理方式的统一。
# 3、@Slf4j
是由lombok
提供的一个注解,其底层还是使用slf4j,将logback的log声明,封装为注解,简化代码开发。
在使用该注解时,需要在IDE中添加lombok插件,侵入性很强。阿里规范不推荐使用lombok。
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TestClass {
}
2
3
4
5
6
# 三、SpringBoot日志
SpringBoot底层是使用slf4j+logback
的方式进行日志记录。
- logback桥接:logback-classic
SpringBoot也把其他的日志都替换成了slf4j。
log4j 适配: log4j-over-slf4j
jul适配:jul-to-slf4j
这两个适配器都是为了适配Spring的默认日志。
# 日志级别
从低到高:TRACE
,DEBUG
,INFO
,WARN
,ERROR
# 日志格式
默认日志输出如下:
2020‐12‐01 14:01:34.665 TRACE 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 跟踪
2020‐12‐01 14:01:34.665 DEBUG 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 调试
2020‐12‐01 14:01:34.665 INFO 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 信息
2020‐12‐01 14:01:34.665 WARN 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 警告
2020‐12‐01 14:01:34.665 ERROR 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 异常
2
3
4
5
可以使用logging.pattern.console
修改默认的控制的日志格式。默认配置如下:
%clr(%d{${LOG_DATEFORMAT_PATTERN:‐yyyy‐MM‐dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:‐%5p}) %c lr(${PID:‐ }){magenta} %clr(‐‐‐){faint} %clr([%15.15t]){faint} %clr(%‐40.40logger{39}){cyan} %clr(:){fa int} %m%n${LOG_EXCEPTION_CONVERSION_WORD:‐%wEx}
时间部分:日期和时间,精度至毫秒
%clr 内容 {faint} :当前内容的颜色
%d{} :logback的日期显示方式
${value:value2} springboot的占位符 + null条件的表达式(如果value为null,使用value2)
LOG_DATEFORMAT_PATTERN: 系统环境变量中的值,springboot底层会根据对应的配置项将值设置到对应的环境变量中
如: LOG_DATEFORMAT_PATTERN=logging.pattern.dateformat 可以在官网4.7章节中看到对应的关系
{-yyyy-MM-dd HH:mm:ss.SSS} 日期的格式
# 日志文件输出
默认情况下,Spring Boot仅记录到控制台,不写日志文件。
如果除了控制台输出外还想写日志文件,则需要设置一个logging.file.name
或logging.file.path
属性(例如,在中application.properties)。
logging.file.name
可以设置文件的名称, 如果没有设置路径会默认在项目的相对路径下
还可以指定路径+文件名:name: D:/xushu.log
logging.file.path
不可以指定文件名称, 必须要指定一个物理文件夹路径,会默认使用spring.log
# logback配置
# 1、appender节点
appender节点是configuration的子节点,是负责写日志的组件。
appender节点有两个必要属性name和class。name指定appender名称,class指定appender的全限定名。
# 2、encoder节点
负责两件事:一是把日志信息转换成字节数组,二是把字节数组写入到输出流。
目前PatternLayoutEncoder 是唯一有用的且默认的encoder 。
有一个pattern节点,用来设置日志的输入格式。使用“%”加“转换符”方式,如果要输出“%”,则必须用“\”对“%”进行转义。
# 3、pattern节点
转换符说明
转换符 | 说明 |
---|---|
c {length } lo {length } logger {length } | 输出日志的logger名,可有一个整形参数,功能是缩短logger名,设置为0表示只输入logger最右边点符号之后的字符串。 例如: %logger{5} 会将名称mainPackage.sub.sample.Bar 输出结果为m.s.s.Bar |
C {length } class {length } | 输出执行记录请求的调用者的全限定名。 参数与上面的一样。尽量避免使用,除非执行速度不造成任何问题。 |
contextName cn | 输出上下文名称 |
d {pattern } date {pattern } | 输出日志的打印日志,模式语法与java.text.SimpleDateFormat 兼容。例如: %d 输出2006-10-20 14:06:49,812 %date{HH:mm:ss.SSS} 输出 14:06:49.812 |
F / file | 输出执行记录请求的java源文件名。尽量避免使用,除非执行速度不造成任何问题。 |
caller{depth} caller{depth, evaluator-1, ... evaluator-n} | 输出生成日志的调用者的位置信息,整数选项表示输出信息深度。 例如, %caller{2} 输出为0 [main] DEBUG - logging statement Caller+0 at mainPackage.sub.sample.Bar.sampleMethodName(Bar.java:22) Caller+1 at mainPackage.sub.sample.Bar.createLoggingRequest(Bar.java:17) |
L / line | 输出执行日志请求的行号。尽量避免使用,除非执行速度不造成任何问题。 |
m / msg / message | 输出应用程序提供的信息。 |
M / method | 输出执行日志请求的方法名。尽量避免使用,除非执行速度不造成任何问题。 |
n | 输出平台先关的分行符“\n”或者“\r\n”。 |
p / le / level | 输出日志级别。 |
r / relative | 输出从程序启动到创建日志记录的时间,单位是毫秒 |
t / thread | 输出产生日志的线程名。 |
replace(p ){r, t} | p 为日志内容,r 是正则表达式,将p 中符合r 的内容替换为t 。 例如: "%replace(%msg){'\s', ''}" |
格式修饰符
格式修饰符可与转换符共同使用,可选的格式修饰符位于“%”和转换符之间。
- 第一个可选修饰符是
左对齐
标志,符号是减号“-”; - 接着是可选的
最小宽度
修饰符,用十进制数表示。如果字符小于最小宽度,则左填充或右填充,默认是左填充(即右对齐),填充符为空格。如果字符大于最小宽度,字符永远不会被截断。 最大宽度
修饰符,符号是点号"."后面加十进制数。如果字符大于最大宽度,则从前面截断。点符号
“.”后面加减号“-”在加数字,表示从尾部截断。
例如:%-4relative 表示,将输出从程序启动到创建日志记录的时间 进行左对齐 且最小宽度为4。
# 4、logback-spring.xml示例
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<!--设置系统日志根目录-->
<property name="APP_DIR" value="logs"/>
<!-- 服务名称 -->
<property name="APP_NAME" value="my-app"/>
<!--非开发环境生成日志记录方式-->
<springProfile name="!dev">
<!-- 全部日志 -->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- %i索引 -->
<!-- 活动文件名 -->
<fileNamePattern>${APP_DIR}/${APP_NAME}/%d/log-%i.log</fileNamePattern>
<!-- 保存期限为30天 -->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 活动文件的大小,默认值是10MB-->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!-- %p日志级别,%t线程名,%d日期,%c类的全名,%方法名,%L行号,%m输出的信息 -->
<!-- pattern节点,用来设置日志的输入格式:时间 输出日志级别 线程名 类名.方法名 行号 输出信息 -->
<pattern>
[%d{HH:mm:ss.SSS,CTT}] %-5level [%t] %class{36}.%M - %L - %msg%xEx%n
</pattern>
<!-- 记录日志的编码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 错误日志 -->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<appender name="errorLogFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- %i索引 -->
<!-- 活动文件名 -->
<fileNamePattern>${APP_DIR}/${APP_NAME}/%d/err-log-%i.log</fileNamePattern>
<!-- 保存期限为30天 -->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 活动文件的大小,默认值是10MB-->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 过滤只输出ERROR级别的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<!-- %p日志级别,%t线程名,%d日期,%c类的全名,%方法名,%L行号,%m输出的信息 -->
<!-- pattern节点,用来设置日志的输入格式:时间 输出日志级别 线程名 类名.方法名 行号 输出信息 -->
<pattern>
[%d{HH:mm:ss.SSS,CTT}] %-5level [%t] %class{36}.%M - %L - %msg%xEx%n
</pattern>
<!-- 记录日志的编码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
</springProfile>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS,CTT} %5p [%15.15t] %-40.40logger{39} : %m%n</pattern>
</encoder>
</appender>
<!--生产环境配置-->
<springProfile name="prod">
<!-- 生产环境下,将此级别配置为适合的级别,以免日志文件太多或影响程序性能 -->
<root level="INFO">
<appender-ref ref="logFile" />
<appender-ref ref="errorLogFile" />
</root>
</springProfile>
<!--测试环境配置-->
<springProfile name="test">
<root level="INFO">
<appender-ref ref="logFile" />
<appender-ref ref="errorLogFile" />
</root>
</springProfile>
<!--开发环境配置-->
<springProfile name="dev">
<!-- name:打印指定name包下的所有类的日志;additivity:默认为true,将此loger的打印信息向上级传递 -->
<logger name="com.ccc" level="DEBUG" additivity="false">
<appender-ref ref="console" />
</logger>
</springProfile>
</configuration>
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