这篇博客是我参考了很多资料总结出来的,内容很多,希望能帮助到你^_^
可以先看下目录了解下大体内容:
4.2 logback-spring.xml是在哪里加载的?
4.3 logback.xml和logback-spring.xml区别
5.7.3.1 TimeBasedRollingPolicy
5.7.3.2 SizeBasedTriggeringPolicy
5.7.3.3 FixedWindowRollingPolicy
5.7.3.4 SizeAndTimeBasedRollingPolicy
一、日志框架发展史
参考博客:
【精选】Java日志框架的发展历史,你不想了解一下吗_java日志历史-CSDN博客 [Java日志框架的发展历史,你不想了解一下吗]
第1阶段:只有System.out 与System.error
第2阶段:Apache大佬Ceki Gülcü 搞了个 Log4j,爆火
第3阶段:Sun眼红Log4j, 自己制定一套标准JUL (Java Util Logging),没啥人用
第4阶段:Apache不服,也自己制定一套标准JCL (Jakarta Commons Logging), 可以在Log4j和JUL之间切换,但存在问题。
第5阶段,大佬Ceki Gülcü离开Apache公司,觉得JCL不好用,自己搞了个Slf4j (Simple Logging Facade For Java 简单日志门面)。但想要Apache和Sun来对接Slf4j太难,于是弄了桥接包,通过桥接包来帮助Slf4j接口与其他日志库建立关系,这种方式称桥接设计模式。
但仍然存在一个问题:日志配置文件未做统一,假如你的系统使用了Slf4j作为日志接口,使用Log4j作为日志产品,则配置文件需要配2份。
Ceki Gülcü发话: 没事,大家都选择用Slf4j统一吧,我来帮大家统一,没有事是桥接包解决不了的,有的话,那就再来个。
第6阶段:Ceki Gülcü 巨佬觉得市场上的日志标准库都是间接实现Slf4j接口,也就是说每次都需要配合桥接包,也就是之前的日志产品都不是正统的Slf4j的实现。
因此在2006年,Ceki Gülcü 基于Slf4j接口写出了Logback日志标准库
第7阶段:Slf4j+Logback的模式,显然很冲击JCL+Log4j,并且本身Logback确实比Log4j性能更优,设计更为合理,所以,老东家Apache可就坐不住了。
在2012年,Apache直接推出新项目,不是Log4j1.x升级,而是新项目Log4j2,因为Log4j2是完全不兼容Log4j1.x的。
并且很微妙的,Log4j2几乎涵盖Logback所有的特性(这不是对着干是啥~而且还有抄袭的嫌疑哈哈哈),更甚者的Log4j2也搞了分离的设计,分化成log4j-api和log4j-core,这个log4j-api也是日志接口,log4j-core才是日志产品。
二、日志规范
参考博客
https://www.cnblogs.com/yangyongjie/p/16230247.html [Java日志规范]
1、【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 (SLF4J)中的 API,使用门面模式的日志框架。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class SpringUtil implements ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(SpringUtil.class);
}
或者使用lombok注解:
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class SpringUtil implements ApplicationContextAware {
}
2、【强制】所有日志文件至少保存15天,因为有些异常具备以“周”为频次发生的特点。日志文件格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd,例如info.log.2023-04-21
3、【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
错误示例:(如果日志级别是 warn,该日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,但最终日志却没有打印)
log.debug("Processing trade with id: " + id + " and symbol: " + symbol);
解决方法一:打印日志前加上判断:
if (log.isInfoEnabled()) {
log.info("Processing trade with id: " + id + " and symbol: " + symbol);
}
if (log.isWarnEnabled()) {
log.warn("Processing trade with id: " + id + " and symbol: " + symbol);
}
if (log.isDebugEnabled()) {
log.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
解决方法二(推荐使用):使用参数化形式{}占位,[] 进行参数隔离。因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。
log.debug("Processing trade with id: [{}] and symbol: [{}]", id, symbol);
4、【强制】生产环境禁止直接使用 System.out 或 System.err 输出日志或使用 e.printStackTrace()打印异常堆栈。
5、【强制】生产环境禁止打印debug日志
6、【建议】打印日志不要使用JSON.toJSONString()写法,而是通过重写对象的toString()方法(可以使用lombok的 @ToString(callSuper=true))。
错误示例:
log.info("基础数据拉取sc:{},mqJson:{}", dockDTO.getNeighNo(), JSON.toJSONString(amcBaseInfoPo));
正确示例:
log.info("基础数据拉取sc:{},mqJson:{}", dockDTO.getNeighNo(), amcBaseInfoPo);
三、日志级别
参考博客
日志打印的8种级别(很详细)_日志级别-CSDN博客[日志打印的8种级别(很详细)]
log4j定义了8个级别的log,优先级从高到低依次为:
OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL
如果日志设置为L,一个级别为P的输出日志只有当P >= L时日志才会输出。
Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。
四、Logback
参考博客:
logback 日志 介绍和配置详解_logback日志配置-CSDN博客 [logback 日志 介绍和配置详解]
万字详解logback日志框架,再没这么全的了!-腾讯云开发者社区-腾讯云 [万字详解logback日志框架,再没这么全的了!]
logback详解 - 知乎[logback详解]
(三)Logback中的Appender - 程序员大本营[(三)Logback中的Appender]
Java日志框架:logback详解 - 知乎[Java日志框架:logback详解]
logback -- 配置详解 -- 三 -- <encoder>[logback -- 配置详解 -- 三 -- <encoder>]
logback 配置颜色高亮_黑帽子技术的博客-CSDN博客[logback 配置颜色高亮]
LogBack 基本使用_凯凯JAVA的博客-CSDN博客[LogBack 基本使用]
(六)Logback中的Filter_logback filter-CSDN博客[(六)Logback中的Filter]
logback logback.xml常用配置详解(三) <filter>-CSDN博客[logback logback.xml常用配置详解(三) <filter>]
RollingFileAppender详解[RollingFileAppender详解]
logback解析——Appender - 简书[logback解析——Appender]
4.1 Maven引入
Spring Boot 默认使用 SLF4J+Logback 记录日志。
1、springBoot已经帮我们引入了logback包,所以我们不需要再去引用。
2、在项目资源文件夹 resources 下 创建 logback-spring.xml 文件。logback 将会自动读取该配置文件。
logback是springboot自带的日志框架.该框架主要有3个模块:
logback-core:核心代码块
logback-classic:实现了slf4j的api,加入该依赖可以实现log4j的api。
logback-access:访问模块与servlet容器集成提供通过http来访问日志的功能(也就是说不需要访问服务器,直接在网页上就可以访问日志文件,实现HTTP访问日志的功能)。
maven已经帮我们集成好了,只需引用:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
点击进入spring-boot-starter-web,可以看到:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.5.RELEASE</version>
<scope>compile</scope>
</dependency>
点击进入spring-boot-starter包,可以看到:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.2.5.RELEASE</version>
<scope>compile</scope>
</dependency>
点击进入spring-boot-starter-logging,可以看到:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>compile</scope>
</dependency>
4.2 logback-spring.xml是在哪里加载的?
参考博客
springboot 源码阅读之 logback-spring.xml 是在哪里加载的?_logback-spring加载流程-CSDN博客 [springboot 源码阅读之 logback-spring.xml 是在哪里加载的?]
随便在logback-spring.xml写个错误:
<configuration scan="true" scanPeriod="10 seconds" debug="false">
<a></a>
</configuration>
启动报错:
Exception in thread "main" java.lang.IllegalStateException: Logback configuration error detected:
ERROR in ch.qos.logback.core.joran.spi.Interpreter@2:8 - no applicable action for [a], current ElementPath is [[configuration][a]]
at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:169)
at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithConventions(AbstractLoggingSystem.java:80)
at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:60)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:118)
at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:313)
at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:288)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:76)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:53)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:345)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
at com.top.whysu.web.system.SystemWebApplication.main(SystemWebApplication.java:17)
进入LogbackLoggingSystem.java第169行,不断debug发现
1、org.springframework.boot.logging.logback.LogbackLoggingSystem定了4个默认配置文件:
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
}
从这里可以看出来读取顺序是:logback-test.groovy, logback-test.xml, logback.groovy, logback.xml
2、org.springframework.boot.logging.AbstractLoggingSystem这边先判断4个默认配置文件是否存在,如果不存的话,则加上-spring后缀再查询。
4.3 logback.xml和logback-spring.xml区别
参考博客
logback.xml和logback-spring.xml的区别_logback-spring和logback.xml-CSDN博客 [logback.xml和logback-spring.xml的区别]
https://www.cnblogs.com/huangdh/p/16778065.html [logback.xml和logback-spring.xml的区别]
【精选】附加:logback日志组件中,logback.xml和logback-spring.xml的区别;(本篇博客并没有得出明确的结论……)_logback.xml和配置文件的区别’-CSDN博客 [附加:logback日志组件中,logback.xml和logback-spring.xml的区别;]
我们使用SLF4J框架记录日志时,会用到 logback.xml 和 logback-spring.xml 两个不同的配置文件。
1、logback-spring.xml 只有在Spring应用程序运行的时候才生效,即带有@SpringBootApplication注解的类启动的时候才会生效。
如果不是Spring应用程序,而是一个main方法或者一个JUnit的测试方法,要用 logback.xml 来配置。
2、存放的位置不同:
logback-spring.xml存放的位置是在SpringApplication主类所在的项目的resources目录,也就是application.yml或者application.properties所在的目录
logback.xml存放的位置是在你启动的那个类所在的项目的resources目录。
3、二者的加载顺序是:logback.xml—>application.properties—>logback-spring.xml。如果你在logback.xml定义了变量,而恰好这个变量又被写在了application.properties中,那么就有可能会获取不到,因为application.properties在logback.xml的后面执行。这也是为什么springboot推荐使用logback-spring.xml来替代logback.xml来配置。
五、logback-spring.xml格式详解
5.1 完整格式
被这张图吓住了?其实很简单的。
<root>和<logger>可以看做一棵树,root就是根节点,logger就是子节点。
<appender>就是用来定义这些节点的规则。
另外<property>和<contextName>就更简单了。
property就是定义变量,
contextName就是你这棵树的名称。
logback的日志配置文件格式如下所示:
<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>
5.2 configuration
根节点<configuration>,包含下面三个属性:
scan: 默认值为true。当此属性设置为true时,配置文件如果发生改变,将会被重新加载。
scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。如可以设置为scanPeriod="30 seconds"表示每30秒检测一次。
debug: 默认值false。当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。
<configuration scan="true" scanPeriod="30 seconds" debug="false">
</configuration>
5.2.1 定时器是如何生效的?
把debug设置为true
<configuration scan="true" scanPeriod="30 seconds" debug="true">
发现启动的时候打印了一句:
16:09:09,385 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - Setting ReconfigureOnChangeTask scanning period to 30 seconds
打开类 ch.qos.logback.classic.joran.action.ConfigurationAction;
发现的确有个定时器:
5.3 contextName
<contextName>标签作用是用来设置日志上下文的名称,可以用来区分不用应用程序。
5.4 property和springProperty
1、通过property元素可定义变量。它有name和value两个属性。
可以通过“${name}”来使用变量。
<property name="LOG_HOME" value="/home/smart-community-docking/scd/logs"/>
如果你是在windows上启动的话,然后你的项目在D盘,则日志会生成在D:\home\smart-community-docking\scd\logs目录下。
2、使用springProperty的话,值是可以从配置文件application.properties里面取值的。
例如现在有application.propertis定义了值:
spring.application.name=smart-community-docking
则可以通过springProperty获取到该值:
<configuration scan="true" scanPeriod="30 seconds" debug="false">
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="smart-community-docking"/>
<contextName>${APP_NAME}</contextName>
</configuration>
5.5 timestamp
用来获取时间戳字符串。这个属性很少使用。
有两个属性key和datePattern。
key: 标识此<timestamp> 的名字;
datePattern: 设置将当前时间(解析配置文件的时间)转换为字符串的模式,遵循 java.txt.SimpleDateFormat的格式。
<configuration scan="true" scanPeriod="30 seconds" debug="false">
<timestamp key="currentDate" datePattern="yyyy-MM-dd" />
</configuration>
5.6.logger和root
5.6.1 说明
logger:
- name属性:用来指定logger约束的包或者具体类。
- level属性:用于设置日志打印级别。RACE,DEBUG,INFO,WARN,ERROR,ALll和OFF还有INHERITED(NULL)。如果没有设置则默认为(null)会继承上级的级别。
- addtivity属性:是否向上级logger传递打印信息,默认为true。如果设置为true,在输出日志时,你会发现控制台会输出两遍。所以一般设置additivity为false
<!-- 指定包-->
<logger name="com.top.scd.controller"/>
<!-- 指定类-->
<logger name="com.top.scd.controller.UserController" level="DEBUG" additivity="true"/>
-
<root>也是<logger>元素
-
它的name就是ROOT。所以只能配置level属性
-
level属性的取值范围只能取 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF。
-
level默认是DEBUG。
5.6.2.appender-ref
<logger>和<root> 标签下允许有0个或者多个 <appender-ref>
<appender-ref>只有一个ref 用于指定<appender>标签。
5.7 appender
appender用来定义日志的输出格式,过滤规则,以及日志文件如何生成。
有2个属性name 和class。
name当然是appender的名称啦。
class对应的是实现类。目前有如下三种常用的类:ConsoleAppender,FileAppender,RollingFileAppender。你会发现loback-core.jar包下真的有这3个类。
5.7.1 ConsoleAppender
ConsoleAppender:日志输出到控制台,类名ch.qos.logback.core.FileAppender。
属性名 | 类型 | 备注 |
encoder | ch.qos.logback.core.encoder.Encoder | 对日志进行格式化。 使用<pattern>指定格式。具体格式见后面章节。 <charset>指定字符编码。 |
target | String | 有效值为System.out或者System.err,默认为System.out。一般不写。 |
示例:
注意:禁止向除了ConsoleAppender之外的appender配置彩色日志。
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="false">
<!--${CONSOLE_LOG_PATTERN}在这个文件里面引用的-->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
5.7.2 FileAppender
FileAppender:日志输出到文件,类名ch.qos.logback.core.FileAppender。
属性名 | 类型 | 备注 |
file | String | 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值 |
append | boolean | 如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。 |
encoder | ch.qos.logback.core.encoder.Encoder | 对日志进行格式化。 使用<pattern>指定格式。具体格式见后面章节。 <charset>指定字符编码。 |
prudent | boolean | 如果是 true,日志会被安全的写入文件(即使其他的FileAppender也在向此文件做写入操作),效率低,默认是 false。 |
示例:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="false">
<!--定义日志路径,如果是windows启动并且代码在D盘, 则日志会生成于D:\home\smart-community-docking\scd\logs下-->
<property name="LOG_HOME" value="/home/smart-community-docking/scd/logs"/>
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!--日志格式配置-->
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
<!--日志输出路径-->
<file>${LOG_HOME}/logback.log</file>
<!--默认日志被追加到文件结尾-->
<append>true</append>
</appender>
<root level="INFO">
<appender-ref ref="file"/>
</root>
</configuration>
5.7.3 RollingFileAppender
RollingFileAppender:滚动记录文件,FileAppender的子类。
先将日志文件指定到文件,当符合某个条件时,将日志记录到其他文件。
类名:ch.qos.logback.core.rolling.RollingFileAppender。
属性名 | 类型 | 备注 |
file | String | 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值 |
append | boolean | 如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。 |
rollingPolicy | ch.qos.logback.core.rolling.RollingPolicy | 当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 属性class定义具体的滚动策略类 |
triggeringPolicy | ch.qos.logback.core.rolling.TriggeringPolicy | 告知 RollingFileAppender 何时激活滚动。 这个要配合具体的rollingPolicy使用,不同的rollingPolicy会有所不同。目前有用到的就只有SizeBasedTriggeringPolicy。 |
encoder | ch.qos.logback.core.encoder.Encoder | 对日志进行格式化。 使用<pattern>指定格式。具体格式见后面章节。 <charset>指定字符编码。 |
prudent | boolean | 当为true时,不支持FixedWindowRollingPolicy。支持TimeBasedRollingPolicy,但是有两个限制,1不支持也不允许文件压缩,2不能设置file属性,必须留空。 |
这里有如下常用的4种滚动策略:
TimeBasedRollingPolicy,SizeBasedTriggeringPolicy,FixedWindowRollingPolicy,SizeAndTimeBasedRollingPolicy
5.7.3.1 TimeBasedRollingPolicy
时间滚动策略 ch.qos.logback.core.rolling.TimeBasedRollingPolicy
可以基于时间滚动按时间生成日志
属性名 | 类型 | 备注 |
fileNamePattern | String | 定义了归档日志文件的名字。 1、它的值由文件名和%d的占位转换符组成,如果没有指定时间和日期格式,默认为yyyy-MM-dd。(由java.text.SimpleDateFormat进行格式化)。 2、轮转周期通过fileNamePattern推断出来的。可以指定多个 %d,但是只能有一个是主要的,用于推断轮转周期,其它的 %d 占位符必须通过 'aux' 标记为辅助的。 例如:/var/log/%d{yyyy/MM,aux}/myapplication.%d{yyyy-MM-dd}.log 3、也可以指定时区:%d{yyyy-MM-dd_HH-mm, UTC},如果指定的时区timezone不能被识别或者拼写错误的话,将会根据TimeZone.getTimeZone(String)方法指定为 GMT。 举例: /foo.%d 默认%d格式是yyyy-MM-dd,按天滚动。 /%d{yyyy/MM}/foo.log 按月滚动 /%d{yyyy-MM-dd_HH}.log 按小时滚动 /%d{yyyy-MM-dd_HH-mm, UTC}.log 按分钟滚动 /%d{yyyy-MM, aux}/%d.log 按天滚动 |
maxHistory | int | 表示日志文件保存的最大数量。 例如:如果根据fileNamePattern判断出来是按天滚动,该值设为30,则日志文件最多保存30天。 |
totalSizeCap | int | 来控制所有归档文件总的大小。当达到这个大小后,旧的归档文件将会被异步的删除。 使用这个属性时还需要设置 maxHistory 属性。而且,maxHistory 将会被作为第一条件,该属性作为第二条件。 |
cleanHistoryOnStart | boolean | 如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为false。 |
下面是一些例子:
文件命名格式 | 滚动计划 | 备注 |
/wombat/foo.%d | 按日滚动 | %d默认是yyyy-MM-dd格式。 昨天:/wombat/foo.2023-04-22 今天:/wombat/foo.2023-04-23 如果RollingFileAppender设置了<file>属性,则当前的日志会被打印到file配置的文件中。例如如果<file>值为/wombat/aaaa.txt, 则: 昨天:/wombat/foo.2023-04-22 今天:/wombat/aaaa.txt |
TimeBasedRollingPolicy支持归档日志文件自动压缩,如果fileNamePattern的值以.gz或者.zip结尾则可利用该特性。
文件命名格式 | 滚动计划 | 备注 |
/wombat/foo.%d.gz | 按日滚动,归档日志文件会自动GZIP压缩 | 文件格式如下: 昨天:/wombat/foo.2019-05-05.gz 当天:/wombat/foo.2019-05-06 |
示例:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="false">
<!--定义日志路径,使用${LOG_HOME访问}, 如果是windows启动并且代码在D盘, 则日志会生成于D:\home\smart-community-docking\scd\logs下-->
<property name="LOG_HOME" value="/home/smart-community-docking/scd/logs"/>
<!-- 滚动文件的方式生成日志日志文件,文件的存储位置通过file标签指定 -->
<!-- 通过encoder指定日志的生成格式,每个appender的日志格式都可以自定义,不用相同 -->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/test.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level - %msg%n</pattern>
</encoder>
<!--
通过rollingPolicy设置日志滚动的策略,这是使用按照时间滚动
fileNamePattern属性设置滚动生成文件的格式,这里设置的精确到天,也就是按照天滚动,如果时间设置精确到秒,就按秒来滚动
maxHistory属性设定最大的文件数,比如按天滚动,这里设置了30天,在第31天日志生成的时候,第一天的日志就会被删掉
-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--设置按天来滚动,前一天日志打印到23点59分,然后就一直没有请求日志,直到次日的1点才有新的日志进入。
在0点到1点这个时间段,日志文件是不会滚动生成新的日志文件。因为滚动的动作是需要日志写入动作来触发。-->
<fileNamePattern>${LOG_HOME}/test-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
<cleanHistoryOnStart>false</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="file"/>
</root>
</configuration>
5.7.3.2 SizeBasedTriggeringPolicy
观察当前活动文件的大小,如果已经大于了指定的值,它会给 RollingFileAppender 发一个信号触发对当前活动文件的轮转
ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy
属性名 | 类型 | 备注 |
maxFileSize | int | 可以为字节,千字节,兆字节,千兆字节,通过在数值后面指定一个后缀 KB,MB 或者 GB。例如,5000000,5000KB,5MB 以及 2GB 都是有效的,前三个是一样的。(默认值是 10 MB) |
官方的示例这个要放在<triggeringPolicy>触发策略使用,并且和FixedWindowRollingPolicy一起使用,所以这里先不说。
5.7.3.3 FixedWindowRollingPolicy
基于窗口大小的滚动策略。ch.qos.logback.core.rolling.FixedWindowRollingPolicy
这个听起来可能有点难理解,其实说白了就是将归档日志文件到最大了就写到下一个文件里,而窗口大小就是最多允许多少份日志文件。
属性名 | 类型 | 备注 |
fileNamePattern | String | 必须包含“%i”。 假设最小值maxIndex和最大值maxIndex分别为1和2,命名模式为 mylog%i.log,会产生归档文件mylog1.log和mylog2.log。还可以指定文件压缩选项,例如,mylog%i.log.gz 或者log%i.log.zip |
minIndex | int | 窗口下限。下限一般都是1啦 |
maxIndex | int | 窗口上限。一般我们用上限就可以了。 |
示例:(官方示例FixedWindowRollingPolic和SizeBasedTriggeringPolicy一起使用:)
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="false">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>test.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>tests.%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
5.7.3.4 SizeAndTimeBasedRollingPolicy
前面介绍的TimeBasedRollingPolicy是根据时间生成日志,SizeBasedTriggeringPolicy是根据大小来生成日志,但很尴尬的一点是这2个类冲突,没法同时使用。
故而官方提供了基于时间和文件大小的滚动策略:
ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
属性名 | 类型 | 备注 |
fileNamePattern | String | 必选参数。 具体介绍可以看TimeBasedRollingPolicy的介绍。 区别点是必须包含 %i |
maxFileSize | int | 每个文件最大多少。可以指定KB,MB 或者 GB,默认值是 10 MB。 |
maxHistory | int | 表示日志文件保存的最大数量。 例如:如果根据fileNamePattern判断出来是按天滚动,该值设为30,则日志文件最多保存30天。 |
totalSizeCap | int | 可选参数,表示所有归档日志文件的的文件总大小。 假如设置每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天但容量达到20G了也要删除多余的日志。 |
cleanHistoryOnStart | boolean | 可选参数,表示appender应用程序启动时是否应进行日志存档清理,默认为false。 |
示例:(每个文件最多5MB,保存60天的历史记录,但最多20GB。)
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="false">
<property name="LOG_HOME" value="/home/smart-community-docking/scd/logs"/>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${LOG_HOME}/mylog-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 每个文件最多5MB,保存60天的历史记录,但最多20GB -->
<maxFileSize>5MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="file" />
</root>
</configuration>
5.8 encoder说明
encoder表示对参数进行格式化。
<encoder>是0.9.19版本之后引进的,以前的版本使用<layout>,logback极力推荐的是使用<encoder>而不是<layout>
<encoder>节点负责两件事情:
1、把日志信息转换为字节数组
2、把字节数组写到输出流
目前使用的是ch.qos.logback.classic.encoder.PatternLayoutEncoder (是唯一有用的且默认的encoder ),它有一个<pattern>节点,用来设置日志的输入格式。
使用“%”加“转换符”方式。
如果要输出“%”,则必须用“\”对“\%”进行转义。
5.8.1 %n
转换符 | 作用 | 是否避免使用 |
n | 日志换行。 根据使用平台输出\n或\r\n | 否 |
说明:
看到最后一列的标题是"是否避免使用",这是因为这些信息是无法直接拿到的(比如请求行号、调用方法名),logback必须通过一些特殊手段去获取这些数据(比如在日志打印出产生一个堆栈信息),这种操作会比较影响效率,因此除非必要,否则不建议打印这些数据。
5.8.2 %c %lo %logger
转换符 | 作用 | 是否避免使用 |
c{length} lo{length } logger{length} | 输出日志的logger名,有一个形参,功能是缩短logger名,设置为0表示只输入logger最右边点符号之后的字符串。 {length} 限制了总输出长度,如果输出长度不够,尽可能显示类名,压缩包名。 | 否 |
示例:
例如我在com.top.scd.web.system.controller.module.xmga.XmgaController打印了一行日志
package com.toptop.scd.web.system.controller.module.xmga;
@RestController
@RequestMapping("/dockset/sc_xmga")
@Slf4j
public class XmgaController {
@GetMapping("/time")
@ResponseBody
public String time() {
log.warn("aaaaa");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeStr = "厦门公安-当前时间:" + format.format(new Date());
return timeStr;
}
}
logback-spring.xml中配置:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%c-%lo-%logger</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
打印结果:
com.toptop.scd.web.system.controller.module.xmga.XmgaController-com.toptop.scd.web.system.controller.module.xmga.XmgaController-com.toptop.scd.web.system.controller.module.xmga.XmgaController
可以注意到点号也是有被算进来的
pattern格式 | 打印结果 | 打印长度 |
%logger | com.toptop.scd.web.system.controller.module.xmga.XmgaController | 63 |
%logger{0} | XmgaController | 14 |
%logger{1} | c.l.s.w.s.c.m.x.XmgaController | 30 |
%logger{14} | c.l.s.w.s.c.m.x.XmgaController | 30 |
%logger{30} | c.l.s.w.s.c.m.x.XmgaController | 30 |
%logger{32} | c.l.s.w.s.c.m.x.XmgaController | 30 |
%logger{33} | c.l.s.w.s.c.m.xmga.XmgaController | 33 |
%logger{38} | c.l.s.w.s.c.module.xmga.XmgaController | 38 |
%logger{47} | c.l.s.w.s.controller.module.xmga.XmgaController | 47 |
%logger{52} | c.l.s.w.system.controller.module.xmga.XmgaController | 52 |
%logger{54} | c.l.s.web.system.controller.module.xmga.XmgaController | 54 |
%logger{56} | c.l.scd.web.system.controller.module.xmga.XmgaController | 56 |
%logger{61} | c.toptop.scd.web.system.controller.module.xmga.XmgaController | 61 |
%logger{63} | com.toptop.scd.web.system.controller.module.xmga.XmgaController | 63 |
%logger{100} | com.toptop.scd.web.system.controller.module.xmga.XmgaController | 63 |
5.8.3 %C %class
转换符 | 作用 | 是否避免使用 |
C{length} class{length } | 输出日志调用所在类。 length与%logger(简写%c或%lo)的用法相同。 尽量避免使用,除非执行速度不造成任何问题。 | 是 (不被推荐使用) |
5.8.4.%d %date
转换符 | 作用 | 是否避免使用 |
d{pattern} date{pattern} | 输出时间格式,模式语法同 java.text.SimpleDateFormat | 否 |
可以指定日期格式精确到毫秒:%d{yyyy-MM-dd HH:mm:ss.SSS}
5.8.5.%caller
转换符 | 作用 | 是否避免使用 |
caller{depth} | 输出日志的调用者的位置信息,整数选项表示输出信息深度。 | 否 |
<pattern>%caller</pattern>
打印:
Caller+0 at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:49)
Caller+1 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Caller+2 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
Caller+3 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
Caller+4 at java.lang.reflect.Method.invoke(Method.java:498)
<pattern>%caller{1}</pattern>
打印:
Caller+0 at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:49)
5.8.6.%L %line
转换符 | 作用 | 是否避免使用 |
L line | 输出执行日志请求的行号。 尽量避免使用,除非执行速度不造成任何问题。 | 是 |
<pattern>%L</pattern>
5.8.7 %m %msg %message
转换符 | 作用 | 是否避免使用 |
m msg message | 输出应用程序提供的信息 | 否 |
log.warn("aaaaa");
<pattern>%msg</pattern>
打印:
aaaaa
5.8.8.%M %method
转换符 | 作用 | 是否避免使用 |
M method | 数值执行日志请求的方法名。 尽量避免使用,除非执行速度不造成任何问题。 | 是 |
public String time() {
log.warn("aaaaa");
}
<pattern>%method</pattern>
打印:
time
5.8.9 %p %le %level
转换符 | 作用 | 是否避免使用 |
p le level | 输出日志级别 | 否 |
log.warn("aaaaa");
<pattern>%level</pattern>
打印:
WARN
5.8.10 %r %relative
转换符 | 作用 | 是否避免使用 |
r relative | 输出从程序启动到创建日志记录的时间,单位是毫秒 | 否 |
<pattern>%r%n</pattern>
多执行几次,打印:
30675581
30676817
30677965
30679104
30680009
5.8.11.%t %thread
转换符 | 作用 | 是否避免使用 |
t thread | 输出产生日志的线程名 | 否 |
<pattern>%thread</pattern>
打印:
http-nio-8998-exec-1
5.8.12 %replace
转换符 | 作用 | 是否避免使用 |
replace(p){r,t} | p为日志内容, r是正则表达式, 将p中符合r的内容替换为t | 否 |
log.warn("aaaaa");
<pattern>%replace(%msg){"a", "b"}</pattern>
打印结果:
bbbbb
6.8.13 宽度设置
%20logger:当字符数少于20个字符时,则左侧留空白;
%-20logger:当字符数少于20个字符时,则右侧留空白;
%.30logger:当字符数据大于30个时,则截断;
还可以合起来使用,例如:%-20.20logger %20.20class
{length}可指定长度,如%logger{36}
log.warn("123456789");
<pattern>%80logger%n%-80logger%n%.20logger</pattern>
5.8.14.显示设置
%highligth(日志内容):突出显示
%green(日志内容):字体显示为指定颜色
%clr(日志内容){颜色} 设置颜色
5.8.14.1.%clr和defaults.xml
<pattern>%clr(%msg){faint}%n</pattern>
这里启动会报错说找不到%clr对应的转换器。
Exception in thread "main" java.lang.IllegalStateException: Logback configuration error detected:
ERROR in ch.qos.logback.core.pattern.parser.Compiler@79145d5a - There is no conversion class registered for composite conversion word [clr]
ERROR in ch.qos.logback.core.pattern.parser.Compiler@79145d5a - Failed to create converter for [%clr] keyword
解决方法一:
引入springboot的默认日志配置:
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="false">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%clr(%msg){faint}%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
解决方法二:或者手动引入需要的配置:
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="false">
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%clr(%msg){faint}%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
这个文件是怎么冒出来的呢?查看前面的那个default.xml文件就可以看到啦!
5.8.14.2.支持的颜色
%black 黑色
%red 红色
%green 绿色
%yellow 黄色
%blue 蓝色
%magenta 洋红色
%cyan 青色
%white 白色
%gray 灰色
%faint 灰白色
以下为对应加粗的颜色代码
%boldRed
%boldGreen
%boldYellow
%boldBlue
%boldMagenta
%boldCyan
%boldWhite
%highlight 高亮色
5.9 filter过滤规则
5.9.1 LevelFilter
<appender>标签里面可以指定<filter>过滤器,
例如ch.qos.logback.classic.filter.LevelFilter,表示根据日志级别进行过滤。
<level>:设置过滤级别
<onMatch>:用于配置符合过滤条件的操作
<onMismatch>:用于配置不符合过滤条件的操作
举例:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="true">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger{30} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
public String time() {
log.trace("====trace====");
log.debug("====debug====");
log.info("====info====");
log.warn("====warn====");
log.error("====error====");
}
打印结果:
186556 [http-nio-8998-exec-6] INFO c.l.s.w.s.c.m.x.XmgaController - ====info====
5.9.2 ThresholdFilter
临界值过滤器,过滤掉低于指定临界值的日志。
对应的类是ch.qos.logback.classic.filter.ThresholdFilter。
当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。
<level>:设置过滤级别
例如:过滤掉所有低于INFO级别的日志:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="true">
<appender name="CONSOLE"
class="ch.qos.logback.core.ConsoleAppender">
<!-- 过滤掉低于INFO级别的日志-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger{30} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
public String time() {
log.trace("====trace====");
log.debug("====debug====");
log.info("====info====");
log.warn("====warn====");
log.error("====error====");
}
打印结果:
393494 [http-nio-8998-exec-9] INFO c.l.s.w.s.c.m.x.XmgaController - ====info====
393494 [http-nio-8998-exec-9] WARN c.l.s.w.s.c.m.x.XmgaController - ====warn====
393494 [http-nio-8998-exec-9] ERROR c.l.s.w.s.c.m.x.XmgaController - ====error====
5.9.3 EvaluatorFilter
求值过滤器,评估、鉴别日志是否符合指定条件。
对应类是ch.qos.logback.core.filter.EvaluatorFilter。
需要额外的两个JAR包,commons-compiler.jar和janino.jar。
有以下子节点:
<evaluator>鉴别器,常用的鉴别器是ch.qos.logback.classic.boolex.JaninoEventEvaluator
,也是默认的鉴别器。它以任意的java布尔值表达式作为求值条件,求值条件在配置文件解释过成功被动态编译,布尔值表达式返回true就表示符合过滤条件。
<evaluator>有个子标签<expression>,用于配置求值条件
<onMatch>:用于配置符合过滤条件的操作
<onMismatch>:用于配置不符合过滤条件的操作
求值表达式作用于当前日志,logback向求值表达式暴露日志的各种字段:
Name | Type | Description |
event | LoggingEvent | 与记录请求相关联的原始记录事件,下面所有变量都来自event,例如,event.getMessage()返回下面"message"相同的字符串 |
message | String | 日志的原始消息,例如,设有logger mylogger,"name"的值是"AUB",对于 mylogger.info("Hello {}",name); "Hello {}"就是原始消息。 |
formatedMessage | String | 日志被各式话的消息,例如,设有logger mylogger,"name"的值是"AUB",对于 mylogger.info("Hello {}",name); "Hello Aub"就是格式化后的消息。 |
logger | String | logger 名。 |
loggerContext | 日志所属的logger上下文。 | |
level | int | 级别对应的整数值,所以 level > INFO 是正确的表达式。 |
timeStamp | long | 创建日志的时间戳。 |
marker | Marker | 与日志请求相关联的Marker对象,注意“Marker”有可能为null,所以你要确保它不能是null。 |
mdc | Map | 包含创建日志期间的MDC所有值得map。访问方法是: mdc.get("myKey") 。mdc.get()返回的是Object不是String,要想调用String的方法就要强转,例如, ((String) mdc.get("k")).contains("val") .MDC可能为null,调用时注意。 |
throwable | java.lang.Throwable | 如果没有异常与日志关联"throwable" 变量为 null. 不幸的是, "throwable" 不能被序列化。在远程系统上永远为null,对于与位置无关的表达式请使用下面的变量throwableProxy |
throwableProxy | 与日志事件关联的异常代理。如果没有异常与日志事件关联,则变量"throwableProxy" 为 null. 当异常被关联到日志事件时,"throwableProxy" 在远程系统上不会为null |
EvaluatorFilter支持使用 java 代码来作为过滤标准。但需要导入额外的包:
<!-- janino -->
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.1.0</version>
</dependency>
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator> <!-- 默认为 ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
<expression>return message.contains("info");</expression>
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
public String time() {
log.trace("====trace====");
log.debug("====debug====");
log.info("====info====");
log.warn("====warn====");
log.error("====error====");
}
打印:
21809 [http-nio-8998-exec-1] INFO com.toptop.scd.web.system.controller.module.xmga.XmgaController - ====info====
5.9.4 自定义过滤器
自定义类:
package com.toptop.scd.base.common.filter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class SampleFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getMessage().contains("info")) {
return FilterReply.ACCEPT;
} else {
return FilterReply.DENY;
}
}
}
日志配置文件指定过滤器
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="1 seconds" debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.toptop.scd.base.common.filter.SampleFilter" />
<encoder>
<pattern>%-4relative [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
打印结果:
186556 [http-nio-8998-exec-6] INFO c.l.s.w.s.c.m.x.XmgaController - ====info====
六、优化异常堆栈stack日志打印
6.1 未优化前
try {
int i = 1/0;
} catch (Exception e) {
log.error("发生异常", e);
}
打印结果:(打印了很多不需要的堆栈信息)
打印结果:
2023-04-25 10:58:03.757 ERROR 36556 --- [nio-8998-exec-1] c.l.s.w.s.c.module.xmga.XmgaController : 发生异常
java.lang.ArithmeticException: / by zero
at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:50)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
6.2.优化后
1、引入logstash-logback-encoder
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.1.1</version>
</dependency>
注意jdk版本依赖:
2、引入
<conversionRule conversionWord="stack" converterClass="net.logstash.logback.stacktrace.ShortenedThrowableConverter"/>
3、通过%stack{}进行配置
stack{100,16,2048,rootFirst,regex1,regex2,evaluatorName}
参数解释:
建议查看源码 net.logstash.logback.stacktrace.ShortenedThrowableConverter#parseOptions()
- 示例:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<conversionRule conversionWord="stack"
converterClass="net.logstash.logback.stacktrace.ShortenedThrowableConverter"/>
<property name="STE_EXCLUSIONS" value="\$\$FastClassByCGLIB\$\$,\$\$EnhancerBySpringCGLIB\$\$,^sun\.reflect\..*\.invoke,^com\.sun\.
,^sun\.net\.,^net\.sf\.cglib\.proxy\.MethodProxy\.invoke,^org\.springframework\.cglib\.,^org\.springframework\.transaction\.
,^org\.springframework\.validation\.,^org\.springframework\.app\.,^org\.springframework\.aop\.,^java\.lang\.reflect\.Method\.invoke
,^org\.springframework\.ws\..*\.invoke,^org\.springframework\.ws\.transport\.,^org\.springframework\.ws\.soap\.saaj\.SaajSoapMessage\.
,^org\.springframework\.ws\.client\.core\.WebServiceTemplate\.,^org\.springframework\.web\.filter\.,^org\.apache\.tomcat\.
,^org\.apache\.catalina\.,^org\.apache\.coyote\.,^java\.util\.concurrent\.ThreadPoolExecutor\.runWorker,^java\.lang\.Thread\.run$"/>
<!--
stack{100,16,2048,rootFirst,regex1,regex2,evaluatorName}
参数解释: 建议查看源码 net.logstash.logback.stacktrace.ShortenedThrowableConverter#parseOptions() 方法
第1参数: maxDepthPerThrowable:值可以是 full或者short或者int值,表示每个异常最多打印多少个 stackTraceElements 元素
第2参数: shortenedClassNameLength:值可以是 full或者short或者int值,将尝试将类名长度缩短到小于此值
com.huan.springboot.service.ExceptionService 可能会变成 c.h.s.s.ExceptionService
第3参数: maxLength:值可以是 full或者short或者int值,指的输出到日志中整个堆栈最大能存在多少个字符。
后面这些参数没有固定顺序
第4参数: rootFirst: 可选参数,如果使用该参数,值就是 rootFirst ,表示应首先打印堆栈的根本原因
第5参数: inlineHash: 可选参数,如果使用该参数,值就是 inlineHash, 指示应该计算和内联十六进制错误哈希
如果参数都不是上方的类型,那么可能是 evaluator 或者 exclude 类型,这2个都是判断这个 stackTraceElement 是否应该被打印,这2个参数没有顺序关系
evaluator: 值的是需要实现 EventEvaluator<ILoggingEvent> 的类
exclude: 指的是需要排除的正则表达式, 如果存在.需要转义成 \.
-->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %msg%n%stack{100,full,2048,rootFirst,inlineHash,${STE_EXCLUSIONS},}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="stdout"/>
</root>
</configuration>
优化后打印的日志,部分堆栈信息被隐藏:
<#6242a0ba> java.lang.ArithmeticException: / by zero
at com.toptop.scd.web.system.controller.module.xmga.XmgaController.time(XmgaController.java:50)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java)
... 3 frames excluded
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
... 5 frames excluded
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
... 27 frames excluded
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
... 2 frames excluded