一、概述
对于一个完整的项目而言,通过日志可以随时观察系统运行情况,日志功能是必不可少的,平时开发项目的时候想知道程序运行情况一般可以使用sysout.print(),打印一些关键的代码或者通过debug查看运行状态,使用sysout.print()会出现代码多余,于是市场上了出现许多记录运行状态的框架。
二、日志框架
日志框架分为3部分:日志接口、日志适配器、日志库
- JCL (Jakarta Commons Logging)
- SLF4 (SimplefLogging Facade for Java)
- JUL (java.util.logging)
- Log4j是Apache下的一款开源的日志框架
- Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。
- Log4j2是对Log4j的升级版,参考了logback一些优秀的设计
日志接口:
- JCL (Jakarta Commons Logging)
- SLF4 (SimplefLogging Facade for Java)
这些框架都是日志门面,类似JDBC本身自己不干活,就是一套接口规范,让调用者不需要关心日志底层具体是什么框架在干活
日志库:也就是真实干活的人,几种常见的日志库
- JUL (java.util.logging)JDK自带的日志工具类Java自带日志工具java.util.logging.Logger-CSDN博客
- Log4j是Apache下的一款开源的日志框架
- Log4j2是对Log4j的升级版,参考了logback一些优秀的设计。Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件,甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;我们也可以控 制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。 Log4j由三个重要的组成构成:日志记录器(Loggers),输出端(Appenders)和日志格式化器(Layout)。 1.Logger:控制要启用或禁用哪些日志记录语句,并对日志信息进行级别限制 2.Appenders : 指定了日志将打印到控制台还是文件中 3.Layout : 控制日志信息的显示格式 Log4j 中将要输出的 Log 信息定义了 5 种级别,依次为 DEBUG、INFO、WARN、ERROR 和 FATAL,当输出时,只有级别高过配置中规定的 级别的信息才能真正的输出,这样就 很方便的来配置不同情况下要输出的内容,而不需要更改代码。
- Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。 Logback 主要由三个模块组成:logback-core,logback-classic。logback-access logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制。logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J; logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与HTTP 访问相关的功能,具有以下优点
- 同样的代码路径,Logback 执行更快 更充分的测试
- 原生实现了 SLF4J API(Log4J 还需要有一个中间转换层)
- 内容更丰富的文档
- 支持 XML 或者 Groovy 方式配置
- 配置文件自动热加载
- 从 IO 错误中优雅恢复
- 自动删除日志归档
- 自动压缩日志成为归档文件
- 支持 Prudent 模式,使多个 JVM 进程能记录同一个日志文件
- 支持配置文件中加入条件判断来适应不同的环境
- 更强大的过滤器
- 支持 SiftingAppender(可筛选 Appender)
- 异常栈信息带有包信息
日志适配器:它是解决日志接口和日志库接口不兼容的,一般配套的都是兼容的
所以我们的目标是挑选一个日志门面,再挑选一个日志库,搭配使用即可,在Springboot项目中整合日志框架有两套方案
- slf4j+Logback
- slf4j+Log4j2
三、slf4j+Logback
Springboot默认抽象接口层使用Slf4j(spring框架是JCL),实现层用Logback,当sprinboot项目引入spring-boot-starter
的时候,就默认帮我们引入了logback
、slf4j
当创建一个boot项目,没有任何其它配置,就看到在控制台下打印日志,Logback默认打印debug级别日志,但是我们注意到debug级别的日志没有记录下来,那是因为Spring Boot为Logback提供了默认的配置文件base.xml以及两个输出端的配置文件console-appender.xml和file-appender.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>
可以看到base.xml引用了console-appender.xml和file-appender.xml这两个配置文件。可以看到 Spring boot已通过将根记录器设置为INFO来覆盖 Logback 的默认日志记录级别, 所以没有看到debug消息的原因。
Springboot日志框架配置有以下两种方式
第一种:简单配置
小项目可以直接在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
:文件最大50MBMaxHistory
:保留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记录日志方式
上面分别介绍了boot整合日志框架的两种方式,下面介绍在业务代码中如何记录业务日志
1、使用记录器logger记录
- 考虑好异常属于什么级别的异常,再打日志。想好是
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
boot就是这么做的,这里主要总结一下日志抽象类SLF4j和其他日志实现类的使用
官网:SLF4J
使用方法,官网的一张图说的很明白
问题:x系统使用Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx框架实现,每个框架有不同的日志记录框架,如何统一使用都使用slf4j
解决方法
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