日志LOG

JAVA
日志框架 日志门面
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");
1
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");
1
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

jul

sun jakarta commons logging

JCLjdk提供的日志门面,其只提供log接口,具体的日志实现,则在运行时动态寻找。

​ JCL是自己去找(ClassLoader),先找到哪个运行哪个。

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Log log =LogFactory.getLog(Test.class);
log.trace('trace');
1
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

SLF4J是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现。

​ slf4j跟JCL机制不一样,其本身不去找,而是由提供桥接器适配器

  • 桥接器:不同的日志实现有其对应的桥接器,提供那个(引入那个包),就运行那个,从而实现具体的日志实现。
  • 适配器:由JCL门面,转换至slf4j。

slf4j-api

​ 如图所示,应用调了sl4j-api,即日志门面接口。日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体 的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。

​ 由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合,可能需要对应的桥接器,上图红框中的组件即是对应的各种桥接器!

​ 我们在代码中需要写日志,变成下面这么写:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("info");
1
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 {
}
1
2
3
4
5
6

# 三、SpringBoot日志

SpringBoot底层是使用slf4j+logback的方式进行日志记录。

  1. logback桥接:logback-classic

SpringBoot也把其他的日志都替换成了slf4j。

  1. log4j 适配: log4j-over-slf4j

  2. jul适配:jul-to-slf4j

    这两个适配器都是为了适配Spring的默认日志。

boot

# 日志级别

从低到高:TRACEDEBUGINFOWARNERROR

# 日志格式

默认日志输出如下:

20201201 14:01:34.665 TRACE 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 跟踪 
20201201 14:01:34.665 DEBUG 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 调试 
20201201 14:01:34.665 INFO 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 信息 
20201201 14:01:34.665 WARN 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 警告 
20201201 14:01:34.665 ERROR 10072 ‐‐‐ [ main] com.tulingxueyuan.Application : 异常
1
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}
1
  • 时间部分:日期和时间,精度至毫秒

  • %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.namelogging.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>
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