重学springboot系列之日志框架与全局日志管理(代码片段)

大忽悠爱忽悠 大忽悠爱忽悠     2023-02-17     341

关键词:


日志框架的体系结构

五花八门的日志工具包

日志框架

  • JDK java.util.logging 包:java.util.logging 是 jdk1.4 发布的 java 日志包,可以说是应用比较久远的日志工具包
  • log4j: apache 的一个开源项目,提供了强有力的 java 日志支持,甚至他也提供了其他语言包括
    C、C++、.Net、PL/SQL 的接口,从而实现多语言并存的分布式环境日志打印。目前已经停止更新,所以不推荐使用。
  • Logback:由log4j创始人设计的另一个开源日志组件,作为Spring Boot默认的日志框架,应用比较广泛。
  • log4j2 :Apache Log4j2是对Log4j的升级,它比其前身Log4j1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些问题。它基于LMAX公司开发Disruptor(一个开源的无锁并发框架),改善了Log4j和Logback在架构设计方面的缺陷,具有超高的吞吐量和低延迟,性能比Log4j1.x和Logback更好。

日志门面

  • commons-logging: Apache commons类库中的一员,他作为一个日志门面,能够自动选择使用 log4j 还是JDK logging,但是他不依赖Log4j,JDK Logging的API。如果项目的classpath中包含了log4j的类库,就会使用log4j,否则就使用JDK Logging。
  • SLF4J:可以说是目前应用最为广泛的日志门面了,它提供了一个日志抽象层,允许你在后台使用任意一个日志类库。如:log4j、log4j2、logback

日志门面存在的意义

为什么不直接使用日志框架,而是搞出一个日志门面?

日志门面(SLF4J)主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架来实现,例如log4j和logback等。 对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、log4j2、logback等),中间使用桥接器完成桥接。

前面介绍的几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性要求。有了SLF4J这个门面之后,程序员永远都是面向SLF4J编程,可以实现简单快速地替换底层的日志框架而不会导致业务代码需要做相应的修改

在使用 SLF4J 进行日志记录时,通常都需要在每个需要记录日志的类中定义 Logger 变量,如下所示:

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

@RestController
public class LogTestController 
    private static final Logger logger = LoggerFactory.getLogger(LogTestController.class);

    @GetMapping("/test")
    public void test()
        logger.trace("Trace 日志...");
        logger.debug("Debug 日志...");
        logger.info("Info 日志...");
        logger.warn("Warn 日志...");
        logger.error("Error 日志...");
    

这显然属于重复性劳动,降低了开发效率,如果你在项目中引入了 Lombok,那么可以使用它提供的 @Slf4j 注解来自动生成上面那个变量,默认的变量名是 log,如果我们想采用惯用的 LOGGER 变量名,那么可以在工程的 main/java 目录中增加 lombok.config 文件,并在文件中增加 lombok.log.fieldName=LOGGER 的配置项即可


日志框架选型

  • Spring Boot 默认的日志记录框架使用的是 Logback
  • 其中 Log4j 可以认为是一个过时的函数库,已经停止更新,不推荐使用,相比之下,性能和功能也是最差的。
  • logback 虽然是 Spring Boot 默认的,但性能上还是不及 Log4j2,因此,在现阶段,日志记录首选Log4j2。

SLF4J + Log4j2 是我们推荐的日志记录选型。

性能测试结果


Log4j2官网


日志级别

细说各日志框架整合配置前,我们先来大致了解下,最常见的日志的几个级别:ERROR, WARN, INFO, DEBUG和TRACE。像其他的,比如ALL、OFF和FATAL之类的开发过程中应该基本上是不会涉及的。所以以下从低到高一次介绍下常见的日志级别。

  • TRACE:追踪。一般上对核心系统进行性能调试或者跟踪问题时有用,此级别很低,一般上是不开启的,开启后日志会很快就打满磁盘的。
  • DEBUG:调试。这个大家应该不陌生了。开发过程中主要是打印记录一些运行信息之类的。
  • INFO:信息。这个是最常见的了,大部分默认就是这个级别的日志。一般上记录了一些交互信息,一些请求参数等等。可方便定位问题,或者还原现场环境的时候使用。此日志相对来说是比较重要的。
  • WARN:警告。这个一般上是记录潜在的可能会引发错误的信息。比如启动时,某某配置文件不存在或者某个参数未设置之类的。
  • ERROR:错误。这个也是比较常见的,一般上是在捕获异常时输出,虽然发生了错误,但不影响系统的正常运行。但可能会导致系统出错或是宕机等。

日志级别从小到大为trace<debug<info<warn<error<fatal,由于通常日志框架默认日志级别设置为INFO,因此上面样例trace和debug级别的日志都看不到。

2020-08-17 13:59:16.566  INFO c.z.b.l.controller.LogTestController     : Info 日志...
2020-08-17 13:59:16.566  WARN  c.z.b.l.controller.LogTestController     : Warn 日志...
2020-08-17 13:59:16.566 ERROR  c.z.b.l.controller.LogTestController     : Error 日志...

常见术语概念解析

  • appender:主要控制日志输出到哪里,比如:文件、数据库、控制台打印等
  • logger: 用来设置某一个包或者具体某一个类的日志打印级别、以及指定appender
  • root:也是一个logger,是一个特殊的父logger。所有的子logger最终都会将输出流交给root,除非在子logger中配置了additivity=“false”。
  • rollingPolicy:所有日志都放在一个文件是不好的,所以可以指定滚动策略,按照一定周期或文件大小切割存放日志文件。
  • RolloverStrategy:日志清理策略。通常是指日志保留的时间。
  • 异步日志:单独开一个线程做日志的写操作,达到不阻塞主线程的目的。

  • 同步日志,主线程要等到日志写磁盘完成之后,才能继续向下执行
  • 异步日志,主线程写日志只是将日志消息放入一个队列,之后就继续向下执行,这个过程是在内存层面完成的。之后由专门的线程从队列中获取日志数据写入磁盘,所以不阻塞主线程。主线程(核心业务代码)执行效率很高。

logback日志框架配置

logback既可以通过application配置文件进行日志的配置,又可以通过logback-spring.xml进行日志的配置。通常情况下,使用全局配置文件application.yml或properties进行配置就足够了,如果您的日志输出需求特别复杂而且需求比较个性化,可以考虑使用logback-spring.xml的配置方式。


application配置文件实现日志配置

我们可以在applicaition.properties(yml) 文件中进行日志的配置

logging:
  level:
    root: info
    com.dhy.boot.launch.controller: debug
  file:
    path: D:\\logs
    name: D:\\logs\\boot-launch.log
    max-size: 10MB
    max-history: 10
  pattern:
    console: '%red(%dyyyy-MM-dd HH:mm:ss) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger10) - %cyan(%msg%n)'
    file: '%dyyyy-MM-dd HH:mm:ss.SSS %-5level [%thread] %logger : %msg%n'
  • logging.level.root=info指定整个系统的默认日志级别是info,日志级别统一化
  • logging.level.com.dhy.boot.launch.controller=debug,指定某个特定的package的日志级别是debug,日志级别个性化。优先级角度,个性配置大于统一配置。
  • logging.file.path将日志输出到指定目录,如果不指定logging.file.name,日志文件的默认名称是spring.log。配置了logging.file.name之后,logging.file.path配置失效。
  • 无论何种设置,Spring Boot都会自动按天分割日志文件,也就是说每天都会自动生成一个新的log文件,而之前的会自动打成GZ压缩包。# 日志文件大小
  • 可以设置logging.file.max-size=10MB分割的每个日志的文件最大容量,超过这个size之后日志继续分隔。
  • 可以设置保留的日志时间logging.file.max-history=10,以天为单位
  • logging.pattern.file输出到文件中的日志的格式
  • logging.pattern.console控制台输出日志的格式,为了在控制台调试时候显示效果更清晰,为日志增加了颜色。red、green等等

这里日志文件会自动创建,无需手动指定


日志格式占位符

配合这张图,看一下占位符和logging.pattern.console格式配置之间的关系

  • %dHH:mm:ss.SSS:日志输出时间(red)
  • %thread:输出日志的进程名字,这在Web应用以及异步任务处理中很有用 (green)
  • %-5level:日志级别,并且使用5个字符靠左对齐 (highlight高亮蓝色)
  • %logger:日志输出类的名字 (boldMagenta粗体洋红色)
  • %msg:日志消息 (cyan蓝绿色)
  • %n:平台的换行符

使用logback-spring.xml实现日志配置

需求

一般情况下,使用全局配置文件application.yml或properties进行配置就足够了,如果您的日志输出需求特别复杂,可以考虑使用logback-spring.xml的配置方式。

spring boot 用自带的logback打印日志,多环境打印:

  • 生产环境输出到控制台和文件,一天一个文件,保留30天.
  • 开发环境输出到控制台和打印sql(mybatis)输出,生产环境不打印这个信息
  • 测试环境只输出到控制台。不输出到文件

打印Mybatis SQL,只需要把使用到Mybatis的package的日志级别调整为DEBUG,就可以将SQL打印出来。

前提:项目已经支持application.yml的profile多环境配置


需求实现

因为logback是spring boot的默认日志框架,所以不需要引入maven依赖,直接上logback-spring.xml放在resources下面

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--引入默认的一些设置-->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!--web信息-->
    <logger name="org.springframework.web" level="info"/>

    <!--写入日志到控制台的appender,用默认的,但是要去掉charset,否则windows下tomcat下乱码-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>$CONSOLE_LOG_PATTERN</pattern>
        </encoder>
    </appender>

    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_PATH" value="D:/logs/boot-launch"/>
    <!--写入日志到文件的appender-->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名,每天一个文件-->
            <FileNamePattern>$LOG_PATH.%dyyyy-MM-dd.log</FileNamePattern>
            <!--日志文件保留天数-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%dyyyy-MM-dd HH:mm:ss.SSS [%thread] %-5level %logger50 - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!--异步写日志到文件-->
    <appender name="asyncFileAppender" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>500</queueSize>
        <appender-ref ref="FILE"/>
    </appender>

    <!--生产环境:打印控制台和输出到文件-->
    <springProfile name="prod">
        <root level="info">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="asyncFileAppender"/>
        </root>
    </springProfile>

    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <!-- 打印sql -->
        <logger name="com.dhy.boot.launch" level="DEBUG"/>
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <!--测试环境:打印控制台-->
    <springProfile name="test">
        <root level="info">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>
</configuration>

异步日志配置:

  • 异步日志queueSize 默认值256,异步日志队列的容量。
  • discardingThreshold:当异步日志队列的剩余容量小于这个阈值,会丢弃TRACE, DEBUG or INFO级别的日志。如果不希望丢弃日志(即全量保存),那可以设置为0。但是当队列占满后,非阻塞的异步日志会变成阻塞的同步日志。所以在高并发低延迟要求的系统里面针对不重要的日志可以设置discardingThreshold丢弃策略,值大于0。

测试一下

上面配置完成之后,可以使用如下代码测试一下,是否满足需求

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

@RestController
public class LogTestController 
    private static final Logger logger = LoggerFactory.getLogger(LogTestController.class);

    @GetMapping("/testlog")
    public void test()
        logger.trace("Trace 日志...");
        logger.debug("Debug 日志...");
        logger.info("Info 日志...");
        logger.warn("Warn 日志...");
        logger.error("Error 日志...");
    


log4j2日志框架整合与使用

引入maven依赖

Spring Boot默认使用LogBack,但是我们没有看到显示依赖的jar包,其实是因为所在的jar包spring-boot-starter-logging都是作为spring-boot-starter-web或者spring-boot-starter依赖的一部分。

如果这里要使用Log4j2,需要从spring-boot-starter-web中去掉spring-boot-starter-logging依赖,同时显示声明使用Log4j2的依赖jar包,具体如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

另外log4j是之前使用比较广泛的软件,容易与log4j2发生冲突,如果冲突将log4j从相应的软件里面排除掉,比如:dozer

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.4.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

添加配置文件log4j2-spring.xml

在resources目录下新建一个log4j2-spring.xml文件,放在src/main/resources目录下即可被Spring Boot应用识别。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <properties>
        <!--日志输出位置-->
        <property name="LOG_HOME">D:/logs</property>
    </properties>

    <Appenders>
        <!-- 将日志输出到控制台-->
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <!--设置日志格式及颜色-->
            <PatternLayout
                    pattern="[%style%dbright,green][%highlight%p][%style%tbright,blue][%style%Cbright,yellow]: %msg%n%style%throwablered"
                    disableAnsi="false" noConsoleNoAnsi="false"/>
        </Console>

        <!-- 将日志输出到文件-->
        <RollingFile name="FILE-APPENDER"
                     fileName="$LOG_HOME/log4j2-demo.log"
                     filePattern="$LOG_HOME/log4j2-demo-%dyyyy-MM-dd-%i.log">
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>[%d][%p][%t][%C] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置最大存档数-->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <!-- 根日志设置 -->
        <Root level="debug">
            <AppenderRef ref="CONSOLE" level="debug"/>
            <AppenderRef ref="FILE-APPENDER" level="info"/>
        </Root>

        <!--spring日志-->
        <Logger name="org.springframework" level="info"/>
        <!-- mybatis日志 -->
        <Logger name="com.mybatis" level="warn"/>
    </Loggers>
</configuration>
  • 上文两个Appender,一个叫做CONSOLE用于输出日志到控制台,一个叫做FILE-APPENDER输出日志到文件
  • PatternLayout用于指定输出日志的格式,[%d][%p][%t][%C] %m%n这些占位符将结合下文测试结果为大家介绍
  • Policies用于指定文件切分参数

TimeBasedTriggeringPolicy默认的size是1,结合filePattern定

重学springboot系列之异步任务与定时任务(代码片段)

重学SpringBoot系列之异步任务与定时任务实现Async异步任务环境准备同步调用异步调用异步回调为异步任务规划线程池SpringBoot任务线程池自定义线程池优雅地关闭线程池通过@Scheduled实现定时任务开启定时任务方法不同定时方式... 查看详情

重学springboot系列之整合数据库开发框架---中(代码片段)

重学Springboot系列之整合数据库开发框架---中javabean的赋值转换为什么要做javabean赋值转换BeanUtils和Dozer?引入Dozer(6.2.0)自定义类型转换(非对称类型转换)映射localDateTime的问题整合MybatisGenerator操作数据整合M... 查看详情

springboot系列springboot日志框架

注意:本SpringBoot系列文章基于SpringBoot版本v2.1.1.RELEASE进行学习分析,版本不同可能会有细微差别。前言Spring框架选择使用了JCL作为默认日志输出。而SpringBoot默认选择了SLF4J结合LogBack。那我们在项目中该使用哪种日志框架呢?在... 查看详情

springboot系列springboot日志框架

注意:本SpringBoot系列文章基于SpringBoot版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别。前言Spring框架选择使用了JCL作为默认日志输出。而SpringBoot默认选择了SLF4J结合LogBack。那我们在项目中该使用哪种日志框... 查看详情

重学springboot系列之整合数据库开发框架---下(代码片段)

重学Springboot系列之整合数据库开发框架---下mybatis+atomikos实现分布式事务整合jta-atomikos配置多数据源统一事务管理器service层测试mybatisplus+atomikos实现分布式事务遗留问题整合jta-atomikos配置多数据源(调整)Spring事务... 查看详情

重学springboot系列之生命周期内的拦截过滤与监听(代码片段)

重学SpringBoot系列之生命周期内的拦截过滤与监听Servlet域对象与属性变化监听监听器定义与实现使用场景监听器的实现全局Servlet组件扫描注解监听器测试session创建时机Servlet过滤器的实现过滤器过滤器的实现servletspring拦截器及请... 查看详情

重学springboot系列之整合静态资源与模板引擎(代码片段)

重学SpringBoot系列之整合静态资源与模板引擎webjars与静态资源springboot静态资源favicon.ico图标欢迎页面使用WebJars管理css&js1.pom中引入依赖2.访问引入的js文件自动检测依赖的版本测试模板引擎选型与未来趋势javaweb开发经历的几个... 查看详情

重学springboot系列之ehcache缓存,缓存问题(代码片段)

重学SpringBoot系列之EhCache缓存,缓存问题,session共享与redis分布式锁EhCache缓存整合SpringCache与Ehcache缓存的使用方法缓存使用中的坑缓存雪崩穿透等解决方案缓存使用的若干问题缓存穿透缓存击穿缓存雪崩redis缓存配置自定... 查看详情

重学springboot系列之服务器推送技术(代码片段)

重学Springboot系列之服务器推送技术主流服务器推送技术说明需求与背景服务端推送常用技术全双工通信:WebSocket服务端主动推送:SSE(ServerSendEvent)websocket与SSE比较服务端推送事件SSE模拟网络支付场景应用场景sse规范模拟实现服务端... 查看详情

重学springboot系列之restful接口及常用注解(代码片段)

重学SpringBoot系列之RestFul接口RESTful接口与http协议状态表述RestFul风格的好处RESTfulAPI的设计风格RESTful是面向资源的(名词)用HTTP方法体现对资源的操作(动词)HTTP状态码Get方法和查询参数不应该改变数据使用复数... 查看详情

重学springboot系列之json处理工具类(代码片段)

重学springboot系列之JSON处理工具类FastJSON、Gson和Jackson对比在Spring中注解方法使用Jackson常用注解手动数据转换BugJackson全局配置FastJSON、Gson和Jackson对比开源的Jackson:SpringBoot默认是使用Jackson作为JSON数据格式处理的类库,Jack... 查看详情

重学springboot系列之redis与springcache缓存(代码片段)

重学SpringBoot系列之redis缓存使用docker安装redis准备工作获取redis镜像创建容器创建持久化存储目录获取redis的默认配置文件模版使用镜像创建一个容器查看活跃的容器访问redis容器服务开启防火墙端口,提供外部访问redis数据结... 查看详情

重学springboot系列之嵌入式容器的配置与应用(代码片段)

重学SpringBoot系列之嵌入式容器的配置与应用嵌入式容器的运行参数配置调整SpringBoot应用容器的参数两种配置方法配置文件方式常用配置参数tomcat性能优化核心参数自定义配置类方式为Web容器配置HTTPS如何生成自签名证书将SSL应... 查看详情

重学springboot系列之整合数据库开发框架---上(代码片段)

重学Springboot系列之整合数据库开发框架整合SpringJDBC操作数据jdbc简介使用jdbc操作数据库的步骤将SpringJDBC集成到Springboot项目springbootjdbc基础代码SpringJDBC多数据源的实现配置多个数据源通过JavaConfig将数据源注入到Spring上下文。Artic... 查看详情

重学springboot系列之基础知识回顾(代码片段)

重学SpringBoot系列之基础知识回顾SpringBoot项目结构SpringBoot、SpringMVC、Spring对比SpringBoot自动配置什么是SpringBootStarter?什么是SpringBootStarterParent嵌入式web容器SpringDataspringboot2.x新特性基础环境升级依赖组件升级默认软件替换新技... 查看详情

重学springboot系列之mockito测试(代码片段)

重学SpringBoot系列之Mockito测试mock中文文档使用Mockito编码完成接口测试编码实现接口测试为什么要写代码做测试?使用接口测试工具Postman很方便啊junit测试框架Mockito测试框架真实servlet容器环境下的测试@SpringBootTest注解@E... 查看详情

日志打印规范(代码片段)

...来处理告警拾遗空指针异常入参校验安全分布式链路追踪springboot全局统一日志打印springboot2.1学习笔记【十九】使用springvalidation实现全局参数校验案例灵魂之问:撸了那么多 查看详情

日志打印规范(代码片段)

...来处理告警拾遗空指针异常入参校验安全分布式链路追踪springboot全局统一日志打印springboot2.1学习笔记【十九】使用springvalidation实现全局参数校验案例灵魂之问:撸了那么多 查看详情