Springboot日志记录方案

时间:2022-10-09 20:59:09


目录

​一、概述​

​二、市面上的日志框架以及日志抽象层类​

​三、slf4j+Logback​

​第一种:简单配置​

​第二种:通过logback专有的xml配置文件详细配置​

​logback.xml结构​

​属性​

​property​

​contextName​

​appender负责写日志的组件​

​logger​

​root​

​案例logback.xml​

​四、slf4j+Log4j2​

​五、Springboot记录日志方式​

​1、使用记录器logger记录​

​2、通过AOP思想记录日志​

​六、如何使用其他日志框架 ​


一、概述

对于一个完整的项目而言,通过日志可以随时观察系统运行情况,日志功能是必不可少的,平时开发项目的时候想知道程序运行情况一般可以使用sysout.print(),打印一些关键的代码或者通过debug查看运行状态,使用sysout.print()会出现代码多余,于是市场上了出现许多记录运行状态的框架。

二、市面上的日志框架以及日志抽象层类

日志框架分为3部分:​​日志门面​​​、​​日志适配器​​​、​​日志库​

​日志门面​​​:JCL、SLF4j、jboss-logging这些框架都是日志门面。门面设计模式是面向对象的一种设计模式,类似JDBC,也就是说这些货本身自己​​不干活​​,就是一套接口规范,让调用者不需要关心日志底层具体是什么框架在干活

​日志库​​:log4j、logback、JUL、log4j2都是日志库,也就是真实干活的人

​日志适配器:​​​它是解决​​日志门面​​​和​​日志库​​接口不兼容的,一般配套的都是兼容的

所以我们的目标是挑选一个日志门面,再挑选一个日志库,搭配使用即可,

​Log4j2是Log4j升级版​​​、​​Log4j2​​是Apache写的一套框架,由于太优秀太过复杂,因此也比较小众(优秀也有错…)

​JUL太简陋了基本没人用​

Springboot项目中整合日志有两套方案

​slf4j+Log4j2、slf4j+Logback​​  

三、slf4j+Logback

在springboot中,boot默认抽象接口层使用slf4j,实现层用logback,创建了一个demo项目​​,​​​当我们引入​​spring-boot-starter​​​的时候,就默认帮我们引入​​logback​​​、​​slf4j,​

当我们创建一个boot项目,我们没有配置任何其它配置,就看到在控制台下打印日志,Logback默认打印debug级别日志,但是我们注意到debug级别的日志没有记录下来,那是因为Spring Boot为Logback提供了默认的配置文件,base.xml,另外Spring Boot 提供了两个输出端的配置文件console-appender.xml和file-appender.xml,base.xml引用了这两个配置文件。所以Springboot默认日志级别是info,可以看到base.xml引用两个配置,

以下是从git上找到spring-boot用的base.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
Base logback configuration provided for compatibility with Spring Boot 1.1
-->
<included>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</included>

在这里, 您可以看到 Spring boot已通过将根记录器设置为INFO来覆盖 Logback 的默认日志记录级别, 这是我们在上面的示例中没有看到调试消息的原因。

Springboot日志框架配置就相当讲究了,就比如我们公司要求,所有日志必须保存180天,每个文件最大50mb,超过的另外取一个文件。​​info​​​、​​warn​​​、​​error​​必须分开。毕竟跟学生项目不同,公司是有审计要求的,不能胡来,那么我就按这个标准配置一下

第一种:简单配置

小项目可以直接在application.properties中对logback简单配置,如下

#修改指定包下的日志输出级别
logging.level.com.javayihao=trace
#当前项目下生成mylog.log日志记录输出的日志
#logging.file=mylog.log
#在指定的路径下输出日志
#logging.file=f:/mylog.log
#当前的磁盘根路径下创建spring文件家和里面的log文件夹,以spring.log作为日志文件名字
logging.path=/spring/log
#指定控制台输出日志的格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS}+++[%thread] %-5level %logger{50} - %msg%n
#执行日志文件中输出日志的格式
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS}===[%thread] %-5level %logger{50} - %msg%n

第二种:通过logback专有的xml配置文件详细配置

通过application.properties文件配置Logback,对于大多数Spring Boot应用来说已经足够了,但是对于一些大型的企业应用来说似乎有一些相对复杂的日志需求。在Spring Boot中你可以在logback.xml或者在logback-spring.xml中对Logback进行配置,相对于logback.xml,logback-spring.xml更加被偏爱。如果是其他名字,只需在application.propertions

中配置logging.config=classpath:logback-boot.xml使用自己的日志配置文件即可

logback.xml结构

<configuration scan="true" scanPeriod="60 seconds" debug="false">  
<property name="glmapper-name" value="glmapper-demo" />
<contextName>${glmapper-name}</contextName>


<appender>
//xxxx
</appender>

<logger>
//xxxx
</logger>

<root>
//xxxx
</root>
</configuration>

属性

  • scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
  • scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  • debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

property

用来定义变量值的标签,​​property​​​标签有两个属性,​​name​​​和​​value​​​;其中​​name​​​的值是变量的名称,​​value​​​的值时变量定义的值。通过​​property​​​定义的值会被插入到​​logger​​​上下文中。定义变量后,可以使“${name}”来使用变量。如上面的​​xml​​所示。

contextName

每个​​logger​​​都关联到​​logger​​​上下文,默认上下文名称为​​“default”​​​。但可以使用​​contextName​​标签设置成其他名字,用于区分不同应用程序的记录

appender负责写日志的组件

<appender name="GLMAPPER-LOGGERONE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

​appender​​​ 有两个属性 ​​name​​​和​​class​​​;​​name​​​指定​​appender​​​名称,​​class​​​指定​​appender​​​的全限定名。上面声明的是名为​​GLMAPPER-LOGGERONE​​​,​​class​​​为​​ch.qos.logback.core.rolling.RollingFileAppender​​​的一个​​appender​​。

appender 的种类

  • ConsoleAppender:把日志添加到控制台
  • FileAppender:把日志添加到文件
  • RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件。它是FileAppender的子类

append 子标签

<append>true</append>

如果是 ​​true​​​,日志被追加到文件结尾,如果是​​false​​​,清空现存文件,默认是​​true​​。

filter 子标签

filter其实是appender里面的子元素。它作为过滤器存在,执行一个过滤器会有返回DENY,NEUTRAL,ACCEPT三个枚举值中的一个。​​appender​​ 有多个过滤器时,按照配置顺序执行。

  • DENY:日志将立即被抛弃不再经过其他过滤器
  • NEUTRAL:有序列表里的下个过滤器过接着处理日志
  • ACCEPT:日志会被立即处理,不再经过剩余过滤器

filter的class有ThresholdFilter以及LevelFilter

ThresholdFilter

临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回​​NEUTRAL​​;当日志级别低于临界值时,日志会被拒绝。

<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>

LevelFilter

级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据​​onMath​​​(用于配置符合过滤条件的操作) 和 ​​onMismatch​​(用于配置不符合过滤条件的操作)接收或拒绝日志。

<filter class="ch.qos.logback.classic.filter.LevelFilter">   
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>

file 子标签

​file​​ 标签用于指定被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。

<file>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
</file>

这个表示当前appender将会将日志写入到​​${logging.path}/glmapper-spring-boot/glmapper-loggerone.log​​这个目录下。

rollingPolicy 子标签

这个子标签用来描述滚动策略的。这个只有​​appender​​​的​​class​​​是​​RollingFileAppender​​时才需要配置。这个也会涉及文件的移动和重命名(a.log->a.log.2018.07.22)。class有TimeBasedRollingPolicy和FixedWindowRollingPolicy

TimeBasedRollingPolicy

最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。这个下面又包括了两个属性:

  • FileNamePattern
  • maxHistory
<rollingPolicy 
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名:按天回滚 daily -->
<FileNamePattern>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
复制代码

上面的这段配置表明每天生成一个日志文件,保存30天的日志文件

FixedWindowRollingPolicy

根据固定窗口算法重命名文件的滚动策略。

encoder 子标签

对记录事件进行格式化。它干了两件事:

  • 把日志信息转换成字节数组
  • 把字节数组写入到输出流
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}
- %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>

目前​​encoder​​​只有​​PatternLayoutEncoder​​一种类型。

appender案例

定义一个只打印error级别日志的appdener

<!-- 错误日志 appender : 按照每天生成日志文件 -->
<appender name="ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<!-- 过滤器,只记录 error 级别的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>error</level>
</filter>
<!-- 日志名称 -->
<file>${logging.path}/glmapper-spring-boot/glmapper-error.log</file>
<!-- 每天生成一个日志文件,保存30天的日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名:按天回滚 daily -->
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-error.log.%d{yyyy-MM-dd}</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 编码 -->
<charset>UTF-8</charset>
</encoder>
</appender>

定义一个输出到控制台的appender

<!-- 默认的控制台日志输出,一般生产环境都是后台启动,这个没太大作用 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
</encoder>
</appender>

logger

用来设置某一个包或者具体的某一个类的日志打印级别以及指定​​appender​

root

根logger,也是一种logger,且只有一个level属性

案例logback.xml

a):统一配置

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<configuration>
<!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,%i索引【从数字0开始递增】,,, -->
<!-- appender是configuration的子节点,是负责写日志的组件。 -->
<!-- ConsoleAppender:把日志输出到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %p (%file:%line\)- %m%n</pattern>
<!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--RollingFileAppender把日志写到文件中-->
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是sys.log -->
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过1KB时,对当前日志进行分割 重命名-->
<appender name="syslog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--当前项目下生成sys.log日志记录输出的日志-->
<File>log/sys.log</File>
<!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 -->
<!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:log/sys.2017-12-05.0.log -->
<fileNamePattern>log/sys.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,可以设置为1KB,查看演示 -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!-- pattern节点,用来设置日志的输入格式 -->
<pattern>
%d %p (%file:%line\)- %m%n
</pattern>
<!-- 记录日志的编码 -->
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<!-- 控制台输出日志级别 -->
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
<!-- com.javayihao为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="com.javayihao" level="DEBUG">
<appender-ref ref="syslog" />
</logger>
</configuration>

b):生产和测试环境分开配置

​可以在application-dev.yml​​​和​​application-pro.yml​​​中分别指明​​不同环境的logback​​配置文件的目录,即可分开,也开使用一个xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />

<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>

<!-- 测试环境+开发环境. 多个使用逗号隔开. -->
<springProfile name="test,dev">
<logger name="org.springframework.web" level="INFO">
<appender-ref ref="FILE"/>
</logger>
<logger name="com.example" level="INFO" />
</springProfile>

<!-- 生产环境. -->
<springProfile name="prod">
<logger name="org.springframework.web" level="ERROR">
<appender-ref ref="FILE"/>
</logger>
<logger name="com.example" level="ERROR" />
</springProfile>
</configuration>

c:)提示信息和异常记录分开记录

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">


<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{50} - %msg%n"/>

<property name="LOG_HOME" value="./log"/>


<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!--<charset>utf8</charset>-->
</encoder>
</appender>


<!--info 级别的日志-->
<!-- 按照每天生成日志文件 -->
<appender name="INFO" 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>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_HOME}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<fileNamePattern>${LOG_HOME}/achiveLog/info-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<!--日志文件保留天数-->
<MaxHistory>180</MaxHistory>
</rollingPolicy>
</appender>


<!--WARN 级别的日志-->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_HOME}/warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<MaxHistory>180</MaxHistory>
</rollingPolicy>
</appender>

<!--ERROR 级别的日志-->
<appender name="ERROR" 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>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_HOME}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/achiveLog/error-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<MaxHistory>180</MaxHistory>
</rollingPolicy>
</appender>

<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO"/>
<appender-ref ref="WARN"/>
<appender-ref ref="ERROR"/>
</root>
</configuration>

首先看一下根节点​​root​​​,这个​​level="INFO"​​​指的是日志登记,按日志级别从低到高分为​​TRACE​​​ < ​​DEBUG​​​ < ​​INFO​​​ < ​​WARN​​​ < ​​ERROR​​​ < ​​FATAL​​​,我们这边指定了​​INFO​​​,也就是说​​DEBUG​​的日志是不会被输出出来的

根节点有4个直接点,挑一个做一下解释

<!--WARN 级别的日志-->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_HOME}/warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<MaxHistory>180</MaxHistory>
</rollingPolicy>
</appender>
  • ​filter​​:用于过滤日志
  • ​level​​​:匹配等级是​​WARN​
  • ​onMatch​​​:当匹配时​​ACCEPT​
  • ​onMismatch​​​:不匹配时​​DENY​​,不匹配的过滤掉

因此只过滤出​​level​​​为​​warn​​​级别的日志,其他的日志信息经过这个节点会被过滤掉,也就不会进入到后续流程了。如果你不写​​onMatch​​​、​​onMismatch​​​标签,那么​​error​​​级别的因为大于​​warn​​,也会进入到后续流程

而我们说的后续流程就是下面的标签。这些标签指明过滤出来的数据应该放到​​控制台​​​还是放到​​文件​​中

  • ​file​​:这些过滤出来的数据需要被写入到文件中
  • ​fileNamePattern​​​:文件的目录和命名格式​​warn-yyyy-mm-dd_1.log​
  • ​maxFileSize​​:文件最大50MB
  • ​MaxHistory​​:保留18天

四、slf4j+Log4j2

1、引入pom

<!--==========war 包 web 工程==========-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--排除 springboot 默认的 logback 依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入 log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!--异步,使用 log4j2 的 AsyncLogger 时需要包含 disruptor-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>



<!--==========jar 包启动工程==========-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<!--排除 springboot 默认的 logback 依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入 log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!--异步,使用 log4j2 的 AsyncLogger 时需要包含 disruptor-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>

2、application.yml

# 引入日志配置文件
logging:
config: classpath:log4j2.xml

3、log4j2.xml 放在目录 resources 下即可

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration 后面的 status,这个用于设置 log4j2 自身内部的信息输出级别,可以不设置,当设置成 trace 时,你会看到 log4j2 内部各种详细输出-->
<!--monitorInterval:Log4j2 能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="error" monitorInterval="30">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

<!--变量配置-->
<Properties>
<!-- 格式化输出:%date 表示日期,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度 %msg:日志消息,%n 是换行符-->
<!-- %logger{36} 表示 Logger 名字最长 36 个字符 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%logger{50}:%L] - %msg%n" />
<!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="/var/log/songo" />
<property name="FILE_NAME" value="songo" />
</Properties>

<appenders>

<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
</console>

<!-- 这个会打印出所有的info及以上级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}/${FILE_NAME}.log" filePattern="${FILE_PATH}/${FILE_NAME}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz" append="true">
<PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/>
<Policies>
<!-- 基于时间的触发策略。该策略主要是完成周期性的log文件封存工作。有两个参数:
interval,integer型,指定两次封存动作之间的时间间隔。单位:以日志的命名精度来确定单位,
比如yyyy-MM-dd-HH 单位为小时,yyyy-MM-dd-HH-mm 单位为分钟
modulate,boolean型,说明是否对封存时间进行调制。若modulate=true,
则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours,
那么假设上次封存日志的时间为00:00,则下次封存日志的时间为04:00,
之后的封存时间依次为08:00,12:00,16:00-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下当天 7 个文件后开始覆盖-->
<DefaultRolloverStrategy max="30">
<!-- 删除处理策略,在配置的路径中搜索,maxDepth 表示往下搜索的最大深度 -->
<Delete basePath="${FILE_PATH}/${FILE_NAME}/" maxDepth="2">
<!-- 文件名搜索匹配,支持正则 -->
<IfFileName glob="*.log.gz" />
<!--!Note: 这里的 age 必须和 filePattern 协调, 后者是精确到 dd, 这里就要写成 xd, xD 就不起作用
另外, 数字最好 >2, 否则可能造成删除的时候, 最近的文件还处于被占用状态,导致删除不成功!-->
<!--7天-->
<IfLastModified age="7d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</appenders>

<!--Logger 节点用来单独指定日志的形式,比如要为指定包下的 class 指定不同的日志级别等。-->
<!--然后定义 loggers,只有定义了 logger 并引入的 appender,appender 才会生效-->
<loggers>
<!--若是 additivity 设为 false,则子 Logger 只会在自己的 appender 里输出,而不会在父 Logger 的 appender 里输出。-->
<!--Console、RollingFileInfo 没有配置 ThresholdFilter,默认走的是 AsyncRoot 的 level 级别,
com.songo.mapper 为我项目 mapper 的包路径,级别设为 debug,可以打印 sql 语句-->
<AsyncLogger name="com.songo.mapper" level="debug" additivity="false">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
</AsyncLogger>
<AsyncLogger name="org.springframework" level="warn" additivity="false">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
</AsyncLogger>
<AsyncRoot level="info" includeLocation="true">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileInfo" />
</AsyncRoot>
</loggers>
</configuration>

4、声明 logger 变量

package com.songo.service;

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

public class TestService {
private static final Logger logger = LoggerFactory.getLogger(TestService.class);

public void Test() {
logger.info("test...");
}
}

5、附自己使用配置文件

<?xml version="1.0" encoding="utf-8"?>
<configuration status="INFO">
<Properties>
<Property name="SYSTEM_LOG_FILE_PATH">${sys:LOG_PATH}/apps/manager</Property>
<Property name="MANAGER_REPORT_LOG_FILE_PATH">${sys:LOG_PATH}/apps/manager</Property>
</Properties>

<Appenders>
<!--应用服务器日志-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d [%t] %-5level %logger{36} - %msg%n"/>
</Console>

<RollingFile name="SYSTEM_INFO_ROLLING_FILE"
fileName="${SYSTEM_LOG_FILE_PATH}/logs.log"
filePattern="${SYSTEM_LOG_FILE_PATH}/%d{yyyy-MM-dd}-info.log">
<PatternLayout>
<Pattern>%d [%t] %-5p [%c] - %m%n</Pattern>
</PatternLayout>
<!-- 文件截断的条件,具体参考文档 -->
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>

<!-- 定义ERROR的Appender -->
<RollingFile name="SYSTEM_ERROR_ROLLING_FILE"
fileName="${SYSTEM_LOG_FILE_PATH}/error-logs.log"
filePattern="${SYSTEM_LOG_FILE_PATH}/%d{yyyy-MM-dd}-error.log">
<!-- 可以通过该参数来设置获取日志的权限 -->
<ThresholdFilter level="ERROR"/>
<PatternLayout>
<Pattern>%d [%t] %-5p [%c] - %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="24"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<!--报表日志-->
<RollingFile name="MANAGER_REPORT_LOG_FILE"
fileName="${MANAGER_REPORT_LOG_FILE_PATH}/report.log"
filePattern="${MANAGER_REPORT_LOG_FILE_PATH}/report.log.%d{yyyy-MM-dd-HH}">
<PatternLayout>
<Pattern>%m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</Appenders>

<Loggers>
<!--控制台是否打印spring和mybatis信息-->
<!-- <logger name="org.mybatis" level="info" additivity="false">-->
<!-- <AppenderRef ref="Console"/>-->
<!-- </logger>-->
<!-- <Logger name="org.springframework" level="info" additivity="false">-->
<!-- <AppenderRef ref="Console"/>-->
<!-- </Logger>-->
<!--同步输出日志-->
<!-- <logger name="com.alliance.adx.controller" level="INFO" additivity="false">-->
<!-- <appender-ref ref="ADX_REPORT_LOG_FILE"/>-->
<!-- </logger>-->
<!--异步输出日志
<AsyncLogger name="com.demo.util.LogUtil" level="INFO" additivity="false">
<appender-ref ref="REPORT_LOG_FILE"/>
</AsyncLogger>-->

<root level="info">
<!--控制台是否打印输出信息-->
<appender-ref ref="Console"/>

<appender-ref ref="SYSTEM_INFO_ROLLING_FILE"/>
<appender-ref ref="SYSTEM_ERROR_ROLLING_FILE"/>
</root>
</Loggers>

</configuration>

 注意:对于AsyncLogger的name属性可以是指定的类,也可以是字符串

//指定类 需要打日志地方直接调用即可
public class LogUitl{
public static final Logger log = LogManager.getLogger(LogUitl.class);
/**
* 记录
*/
public static void record(String str) {
log.info(str);
}
}


//直接使用字符串
public class Test{
//AsyncLogger的name属性也为testLog
public static final Logger log = LogManager.getLogger("testLog");
/**
* 记录
*/
public void test(String str) {
log.info(str);
}
}

五、Springboot记录日志方式

1、使用记录器logger记录

  1. 考虑好异常属于什么级别的异常,再打日志。想好是​​log.error​​​还是​​log.warn​

比如业务异常往往通过用户引导就可以恢复的,那么就只打​​WARN​​​级别。而​​ERROR​​​级别的日志一旦出现,就需要人工介入,因此要定期检查​​ERROR​​日志排查问题。这段是《阿里Java开发手册》写的

public class TestController{
//记录器
Logger logger = LoggerFactory.getLogger(getClass());
@Test
public void contextLoads() {
//日志级别,由低到高,调整日志级别,只输出高级别日志,
logger.trace("这是trace跟踪信息");
logger.debug("这是debug调试信息");
//springboot默认输出info以后的级别,也就是root级别
logger.info("这是info自定义信息");
logger.warn("这是warn警告信息");
logger.error("这是error错误信息");
}
}

看下logger.error代码就知道error有2个重载方法

public void error(String msg);
public void error(String msg, Throwable t);

上面的代码只有一个参数,因此都会被认为是调用第一种方法,这样造成的结果就是e将会被自动转成String类型,从而丢失的许多错误信息、堆栈信息,即不知道哪个方法调用了service产生的错误,也不知道错误的原因,更不知道代码抛出异常的行数。生产一旦出现问题,根本无从排起,

而正确的日志,我们即可以知道发生错误的原因,抛出异常的行数,同时也能获悉堆栈调用的关系,这样排查生产问题才有解决的可能性。

logger.error("x部分出错", e);

2、通过AOP思想记录日志

代码1:

@Aspect//切面
@Component//组件
public class LogAspect {

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

/**
* 这里把切点切再controller(web)层下的每个方法
*/
@Pointcut("execution(* com.javayihao.myweb.controller.*.*(..))")
public void log() {
}


/**
* 执行com.javayihao.myweb.controller下所有的方法之前执行这个方法
* 获取要记录的url ip 请求的方法 以及请求时候所带的参数
* @param joinPoint
*/
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
//类名.方法
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
//参数
Object[] args = joinPoint.getArgs();
//调用自己封装的对象
RequestLog requestLog = new RequestLog(url, ip, classMethod, args);
logger.info("Request : {}", requestLog);
}

@After("log()")
public void doAfter() {
// logger.info("--------doAfter--------");
}

//执行com.javayihao.myweb.controller下所有的方法之后要执行的方法
@AfterReturning(returning = "result",pointcut = "log()")
public void doAfterRuturn(Object result) {
logger.info("Result : {}", result);
}

private class RequestLog {
private String url;
private String ip;
private String classMethod;
private Object[] args;

public RequestLog(String url, String ip, String classMethod, Object[] args) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.args = args;
}

@Override
public String toString() {
return "{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", args=" + Arrays.toString(args) +
'}';
}
}

}

代码2:

@Aspect
@Component
public class LogAopAspect {

private static final Logger logger = LoggerFactory.getLogger(LogAopAspect.class);


@Around("execution(* company.controller.*.*(..))")
public Object process(ProceedingJoinPoint pjp) throws Throwable {

Class<?> currentClass = pjp.getTarget().getClass();
MethodSignature signature = (MethodSignature) (pjp.getSignature());
String ClassName = currentClass.getSimpleName();
String methodName = currentClass.getMethod(signature.getName(), signature.getParameterTypes()).getName();
logger.info("======= 开始执行:" + ClassName + " — " + methodName + " ========");
Object obj = pjp.proceed();
logger.info("======= 执行结束:" + ClassName + " — " + methodName + " ========");
return obj;
}
}

六、如何使用其他日志框架 

项目中如果要记录日志,不应该直接使用一个日志实现类记录,而是通过一个日志抽象层+一个日志抽象实现类,然后调用抽象层里面的接口记录接口,如下使用日志抽象类SLF4j

Springboot日志记录方案

boot就是这么做的,这里主要总结一下日志抽象类SLF4j和其他日志实现类的使用

官网:​​SLF4J​

使用方法,官网的一张图说的很明白

Springboot日志记录方案

问题:x系统使用Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx框架实现,每个框架有不同的日志记录框架,如何统一使用都使用slf4j

Springboot日志记录方案

解决方法

1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现

比如我们要使用slf4j+log4j的方式来记录日志,依赖如下

再如我们要使用slf4j+log4j2的方式来记录日志,依赖如下

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

但是在boot项目中不推荐slf4j+log4j2或者slf4j+log4j组合来记录日志,springboot官方推荐slf4+logback