SpringBoot2 | 第三篇:整合日志框架

日志框架是一套能实现日志输出的工具包,它可以运行时选择性输出,性能优异,配置灵活。它在开发中也是必不可少的工具,这篇笔记记录 SpringBoot 如何整合日志框架。

[TOC]

日志框架的作用

  1. 定制输出目标
    • 一般来说我们都需要把日志输出到文件
    • 某些系统可能还需要定制日志文件的 滚动策略(比如 一天输出一个日志文件)
    • 很多集成系统要输出日志文件到数据库或者是网络的第三方服务
  1. 定制输出格式
    • 可以在不修改代码的情况下通过配置文件自由的定制输出格式
  1. 携带上下文信息
    如:时间戳、类路径、线程等等

最佳搭配

日志门面: SLF4J

日志实现: Logback

==Logback 是默认实现==,所以我们可以不引入依赖

日志级别

1
2
3
4
5
6
7
public enum Level {
ERROR(40, "ERROR"),
WARN(30, "WARN"),
INFO(20, "INFO"), //默认级别
DEBUG(10, "DEBUG"),
TRACE(0, "TRACE");
// 上面最高,下面最低,日志级别从上往下看 `由高到底`

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.0.5.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9
  • SLF4j + Logback

使用

1、HelloWorld

1.1、传统方式

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.fatal;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter3ApplicationTests {

private Logger logger = LoggerFactory.getLogger(getClass());

@Test
public void contextLoads() {
logger.info("Hello World");
}

}
控制台

360截图17860605429642

1.2、注解方式(推荐)

步骤
  1. 添加依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional> <!-- true:依赖该项目不会依赖Lombok -->
    </dependency>
  2. 加上注解 @Slf4j

  3. 方法中直接使用 log 即可

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.fatal;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j // 它是一个的用于日志管理的注解
public class Chapter3ApplicationTests {

@Test
public void contextLoads() {
log.info("Hello World");
}

}
问题

如果 log 找不到。报错 Cannot resolve symbol ‘log’

解决方法
  1. 用快捷键 Ctrl+Alt+S 打开:Settings→Plugins→Browse repositories
  2. 输入lom 后选择 Install,安装插件
  3. 按照提示重启 IDEA 即可

2、默认级别

顺序

trace < debug < info < warn < error (由低到高)

代码

1
2
3
4
5
6
7
8
9
10
11
/**
* 低于默认级别的不会输出
*/
@Test
public void testDefault() {
log.trace("trace...");
log.debug("debug...");
log.info("info..."); // 默认级别,为root级别
log.warn("warn...");
log.error("error...");
}

控制台

360截图17920826112116132

3、日志中输出变量

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 在日志中输出变量,@Slf4j 提供了更加方便的写法
*/
@Test
public void printVariable() {
String username = "fatal";
String password = "123456";

//你可能会这样写,这样子拼接的时候尤其是在变量特别多的时候,你可能会特别的头疼
log.info("username: " + username + ",password: " + password);
//Slf4j提供了一种方便的写法
log.info("username: {} ,password: {} ",username,password);
}

控制台

360截图16270824648957

4、本地输出日志文件

4.1、方式一:application.yml

只能配置日志文件的路径,日志输出的格式等等一些简单的配置

  1. 设置控制台日志的输出格式

    1
    2
    3
    4
    logging:
    pattern:
    console: "%d - %msg%n"
    # "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"

    日志输出格式
    %d:表示日期时间,
    %thread:表示线程名,
    %-5level:级别从左显示5个字符宽度,
    %logger{50}:表示 logger 名字最长 50 个字符,否则按照句点分割,
    %msg:日志消息,
    %n:是换行符

  2. 指定生成配置文件的路径

    1
    2
    logging:
    path: E:/fatal

    360截图1700102010388140

  3. 指定生成配置文件的路径和文件名

    1
    2
    logging:
    file: E:/fatal/fatal.log

    360截图1672040284128132

  4. 设置日志级别

    1
    2
    3
    4
    logging:
    level:
    # com.fatal.Chapter3ApplicationTests: debug # 具体类路径
    com.fatal: debug # 具体包名

4.2、方式二:logback-spring.xml(官方推荐)

可以配置一些比较复杂的配置,如:

  • 区分 infoerror 的日志
  • 每天生产一个日志文件
  1. 设置控制台日志的输出格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>

    <!-- 指定控制台日志输出格式de配置项consoleLog -->
    <appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
    <pattern>
    %d - %msg%n
    </pattern>
    </layout>
    </appender>

    <!-- 使用配置项consoleLog -->
    <root level="info">
    <appender-ref ref="consoleLog"/>
    </root>

    </configuration>
  2. 指定日志文件的输出位置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>

    <!-- 指定日志文件的输位置de配置项fileInfoLog -->
    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 指定日志文件的输出格式 -->
    <encoder>
    <pattern>
    %msg%n
    </pattern>
    </encoder>
    <!-- 滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 路径 -->
    <fileNamePattern>E:/fatal/info/info.%d.log</fileNamePattern>
    </rollingPolicy>
    </appender>

    <!-- 使用配置项 -->
    <root level="info">
    <appender-ref ref="fileInfoLog"/>
    </root>

    </configuration>

    360截图17321122366740

    一般用的话,我需要将 infoerror 两日志文件隔离,而且还需要过滤一些不必要的限制

  • Info日志文件

    加上 指定 LevelFilter 过滤级别

    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
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>

    <!-- 指定日志文件的输位置de配置项fileInfoLog -->
    <appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>INFO</level> <!-- 设置过滤级别 -->
    <onMatch>ACCEPT</onMatch> <!-- 用于配置符合过滤条件的日志的操作 -->
    <onMismatch>DENY</onMismatch> <!-- 用于配置不符合过滤条件的日志的操作 -->
    </filter>
    <!-- 指定日志文件的输出格式 -->
    <encoder>
    <pattern>
    %msg%n
    </pattern>
    </encoder>
    <!-- 滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 路径 -->
    <fileNamePattern>E:/fatal/info/info.%d.log</fileNamePattern>
    </rollingPolicy>
    </appender>

    <!-- 使用配置项-->
    <root level="info">
    <appender-ref ref="fileInfoLog"/>
    </root>

    </configuration>

    FilterReply

    1
    2
    3
    4
    5
    6
    7
    8
    public enum FilterReply {
    DENY, // 拒绝
    NEUTRAL, // 中立
    ACCEPT; // 接受

    private FilterReply() {
    }
    }
  • Error日志文件 (两种方式)

    加上 指定 ThresholdFilter 过滤级别

    ThresholdFilter :不能排除,只能选定某个日志级别。和在 spring 配置文件中设置日志级别差不多,区别是作用范围不同

    作用:输出日志级别为所选的更高的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!-- 指定日志文件的输位置de配置项fileErrorLog -->
    <appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!--<filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>ERROR</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>-->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <level>ERROR</level>
    </filter>
    <!-- 指定日志文件的输出格式 -->
    <encoder>
    <pattern>
    %msg%n
    </pattern>
    </encoder>
    <!-- 滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 路径 -->
    <fileNamePattern>E:/fatal/error/error.%d.log</fileNamePattern>
    </rollingPolicy>
    </appender>

5、指定配置

如果不使用 SpringBoot 的默认配置的话,可以在 resources 放上每个日志框架的配置文件

Logging System Customization
Logback logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy
Log4j2 log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging) logging.properties

logback.xml:直接就被日志框架识别了;

==logback-spring.xml==(官方推荐):日志框架 就不直接加载日志的 默认 配置项,由 SpringBoot 解析 日志配置,可以使用 SpringBoot 的高级 profile 功能

1
2
3
4
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
可以指定某段配置只在某个环境下生效
</springProfile>

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
</springProfile>
</layout>
</appender>

<root level="info">
<appender-ref ref="stdout"/>
</root>

科普logback.xmllogback-spring.xml都可以用来配置 logback,但是两者的加载顺序是不一样的。加载优先级为:logback.xml --> application.properties --> logback-spring.xml.

logback.xml加载早于application.properties,所以如果你在logback.xml使用了变量时(怎么用?${}也就是SpEL取值),而恰好这个变量是写在application.properties时,那么就会获取不到,只要改成logback-spring.xml就可以解决。

5.1、测试 logback-spring.xml 的功能 profile

5.1.1、default 环境

默认是 default 环境

两种方式

一是:直接运行,运行一个测试方法的话,显示的是 ====

二是:使用命令行运行

  1. 打包mvn package

360截图17290429104127106

  1. 进入 target 目录,输入命令java -jar chapter2-0.0.1-SNAPSHOT.jar

360截图16720401215444

5.1.2、dev 环境
三种方式

一是:在 application.yml 文件中添加上

1
2
3
spring:
profiles:
active: dev

点击运行即可

二是:命令行上

打包后,进入 target 目录,输入命令java -jar chapter2-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

360截图17290504575861

三是:修改启动配置项 Edit Configurations…

360截图17290507485450

360截图17860530447977

最后点击启动即可

控制台

360截图18180712416336

6、切换日志框架

切换到 log4j2

排除依赖 spring-boot-starter-logging 后,加上依赖 spring-boot-starter-log4j2

步骤

  1. 打开 pom.xml 文件,快捷键 Shift + Ctrl + Alt + U –> 生成 依赖图表

  2. 找到 spring-boot-starter-logging 后,右键单击选择 Exclude 排除(或用快捷键 Shift + Delete 排除)

    当然也可以自己手动在 xml 文件上排除依赖,但我比较喜欢 图表 这种

    360截图17630324534037

  3. 引入依赖 spring-boot-starter-log4j2

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
  4. log4j2.xml 放在 resources

log4j2.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
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出 -->
<!-- monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数 -->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] ==log4j2== [%p] - %l - %m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
60
</loggers>
61
</configuration>

360截图18141221306775

7、日志颜色编码

如果终端支持 ANSI,默认情况下会给日志上个色,提高可读性,可以在配置文件中设置 spring.output.ansi.enabled 来改变默认值

  • ALWAYS: 启用 ANSI 颜色的输出。
  • DETECT: 尝试检测 ANSI 着色功能是否可用。
  • NEVER: 禁用 ANSI 颜色的输出。

application.yml

1
2
3
4
spring:
output:
ansi:
enabled: always

logback-spring.xml(或logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 指定控制台日志输出格式de配置项consoleLog -->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%black(控制台-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger) - %cyan(%msg%n)
</pattern>
</layout>
</appender>

<!-- 使用配置项 -->
<root level="info">
<appender-ref ref="consoleLog"/>
</root>

运行一个 HelloWorld 测试方法

控制台

360截图16850813798272

总结

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter3

学习 唐亚峰廖师兄 前辈的经验