Spring Boot 系列:日志动态配置详解
世界上最快的捷径,就是脚踏实地,本文已收录架构技术专栏关注这个喜欢分享的地方。
开源项目:
- 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cubic
- 摄像头视频流采集:https://gitee.com/sanjiankethree/cubic-video
一、简介
Spring Boot 版本: 2.3.4.RELEASE
不知道大家有没有过当线上出现问题的时候,需要某些DEBUG日志,但奈何当前使用时INFO。
如果想启用DEBUG就需要重新打包发版,但某些场景下重启有可能问题就不会复现了,真是脑阔疼啊。
今天我们就来说下Spring Boot 下的日志配置动态调整,让你的日志级别随心而动。
Spring Boot的日志
在Spring Boot 内部使用的其实是Commons Logging, 而基于Spring Boot的配置加载机制为我们提供了Java Util Logging、Log4j2、Logback几种日志方式。
Logback是其默认的日志框架,如果没有特殊的必要真不建议更换了。(不要说性能了)
日志格式
不要小瞧格式这种东西,在实际应用的时候是贼拉重要的一件事。
不知道大家的公司有没有统一的日志基础组件,当然没有也大概会有统一的日志配置文件吧。
想想如果你的日志格式不统一的话,如果每个项目都有自己的风格的话,你叫你的运维小伙伴怎么帮你切分日志?帮你报警呢?那真是正则写到死,完全靠爱发电了。(比如我们使用的Loghub 不统一要被运维打死的)
来看下我们的日志格式配置,这里只放PATTERN
-|%d{yyyy-MM-dd HH:mm:ss.SSS}|%-5level|%X{tid}|%thread|%logger{36}.%M:%L-%msg%n
先解释下各个位置:
%d{yyyy-MM-dd HH:mm:ss.SSS}
:时间%-5level
: 日志级别%X{tid}
: 我们自定义的分布式追踪ID%thread
: 线程%logger{36}.%M:%L
:class的全名(36代表最长字符).信息 行号%msg%n
: 输出信息 换行
这里不知道大家能否理解前面有一个 -|
是为了什么? 其实是为了在正则切分的时候方便区分换行的日志,如异常堆栈的信息。
几个知识点
再说几个其他Spring Boot使用的小点,我们就来进入正题
在这里Logback 没有FATAL 级别,被归到ERROR里面了
可以在
application.properties
里面配置debug=true
来开启debug模式,你也可以配置trace=true
开启 trace模式可以再
application.properties
里使用logging.level.<logger-name>=<level>
这种格式来配置各种日志级别,比如org.hibernate
级别被设置为ERRORlogging.level.org.hibernate=error
日志组的概念,如果这么一个个配置烦死了,可以设定一个组给它整体配置。如:
logging.group.tomcat=org.apache.catalina, org.apache.coyote
配置级别为TRACElogging.level.tomcat=TRACE
如果使用Logback配置文件,官方建议使用
logback-spring.xml
这样的名称,如果使用logback.xml
这样的,Spring的日志初始化就该有问题了。如果你控制日志配置,但又不想用logback.xml作为Logback配置的名字,可以在application.properties配置文件里面通过logging.config属性指定自定义的名字:
logging.config=classpath:xxx-log.xml
二、动态修改日志级别
下面我们就来说说在运行状态下的Spring Boot应用是怎么进行动态日志级别变更的
Spring Boot Actuator
Actuator 想必了解过Spring Boot的都知道它的大名,监控、审计、度量信息等等。我们使用的优雅停机、健康检查、日志信息都是通过这玩意来实现的。
在我们使用了Actuator 后,我们就可以使用其Logger的REST接口来操作我们的日志了,有如下三个
GET http://127.0.0.1:6080/actuator/loggers
返回当前应用全部的日志级别信息(想要啥有啥)GET http://127.0.0.1:6080/actuator/loggers/{name}
返回{name}的日志级别POST http://127.0.0.1:6080/actuator/loggers/{name}
配置参数{"configuredLevel":"INFO","effectiveLevel":"INFO"}
修改日志级别
使用Actuator 机制动态修改级别
1)、依赖必要的配置 Spring Boot Actuator (如果你继承了parent是不需要version的)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
2)、配置文件
#注意,这里我只开启了loggers,health两个Actuator
management.endpoints.web.exposure.include=loggers,health
management.endpoint.loggers.enabled=true
3)、创建一个Controller
@RestController
@RequestMapping("/log")
public class TestLogController {
private Logger log = LoggerFactory.getLogger(TestLogController.class);
@GetMapping
public String log() {
log.trace("This is a TRACE level message");
log.debug("This is a DEBUG level message");
log.info("This is an INFO level message");
log.warn("This is a WARN level message");
log.error("This is an ERROR level message");
return "See the log for details";
}
}
4)、启动应用
此时在你启动应用后,如果你使用IDEA,就可以看到如下Mapping,有三个关于logger的REST接口
5)、测试
此时,我先执行GET http://127.0.0.1:6080/log
,控制台打印如下信息
2020-10-15 23:14:51.204 INFO 52628 --- [nio-6080-exec-7] c.q.chapter1.failure.TestLogController : This is an INFO level message
2020-10-15 23:14:51.220 WARN 52628 --- [nio-6080-exec-7] c.q.chapter1.failure.TestLogController : This is a WARN level message
2020-10-15 23:14:51.221 ERROR 52628 --- [nio-6080-exec-7] c.q.chapter1.failure.TestLogController : This is an ERROR level message
此时我们执行GET http://127.0.0.1:6080/actuator/loggers/ROOT
返回{"configuredLevel":"INFO","effectiveLevel":"INFO"}
,代表此时我们应用的日志级别是INFO,ROOT表示的是根节点。
6)、修改级别
此时我们不想再看INFO信息,想将整个应用日志级别换位WARN的话,我们来执行POST http://127.0.0.1:6080/actuator/loggers/ROOT
参数为{"configuredLevel":"TRACE","effectiveLevel":"TRACE"}
此时我们再来执行GET http://127.0.0.1:6080/log
,控制台打印如下信息
2020-10-15 23:24:11.481 TRACE 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController : This is a TRACE level message
2020-10-15 23:24:11.481 DEBUG 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController : This is a DEBUG level message
2020-10-15 23:24:11.481 INFO 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController : This is an INFO level message
2020-10-15 23:24:11.481 WARN 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController : This is a WARN level message
2020-10-15 23:24:11.481 ERROR 53552 --- [nio-6080-exec-3] c.q.chapter1.failure.TestLogController : This is an ERROR level message
另几种方法
配置文件扫描: 就是Logback 自动扫描的特性修改级别,配合
logback-spring.xml
开启<configuration scan="true" scanPeriod="15 seconds">
就可以实现动态修改logback-spring.xml
内部日志级别的目的了,有兴趣可以自己试试。arthas 动态修改
结合远程配置中心,如Apollo实现级别动态修改
三、实现原理
这里我们主要使用的是Spring Boot Actuator Log
,所以我们也就来说说它的原理。
Endpoint的加载
首先我们从依赖spring-boot-actuator
中找到我们的LoggersEndpoint
(所有的Actuator都是这一个路数),如图:
熟悉Spring Boot加载机制的朋友都了解,在每个actuator Endpoint的背后,必然还会存在一个xxxEndpointAutoConfiguration
来为我们进行Endpoint 的加载。
而这些加载机制就都存放在spring-boot-actuator-autoconfigure
中,我们在其中可以找到LoggersEndpointAutoConfiguration
用于加载LoggersEndpoint
的配置类。
来看下它的核心:
@Bean
@ConditionalOnBean(LoggingSystem.class)
@Conditional(OnEnabledLoggingSystemCondition.class)
@ConditionalOnMissingBean
public LoggersEndpoint loggersEndpoint(LoggingSystem loggingSystem,
ObjectProvider<LoggerGroups> springBootLoggerGroups) {
return new LoggersEndpoint(loggingSystem, springBootLoggerGroups.getIfAvailable(LoggerGroups::new));
}
可以看到两个重要的参数:
- LoggingSystem:一个抽象顶级类
- springBootLoggerGroups:存储了当前日志分组数据
总结下:
1、我们依赖了spring-boot-starter-actuator
包后,里面依赖了spring-boot-actuator-autoconfigure
2、在启动扫描到spring-boot-actuator-autoconfigure
下的META-INF/spring.factories
时,LoggersEndpointAutoConfiguration
会被加载到
3、LoggersEndpointAutoConfiguration
内又声明了LoggersEndpoin
并赋值LoggingSystem
和springBootLoggerGroups
作为其参数
4、项目启动后我们通过LoggersEndpoint
接口进行日志数据访问
LoggingSystem
LoggingSystem的继承关系
通过上面可以了解到 LoggingSystem
就是日志操作管理的核心了,所以我们先来看下他的Diagrams
通过继承关系一眼可以看到,LogbackLoggingSystem
就是我们的正主了,虽然说知道了正主那我们的LoggingSystem到底是怎么加载的呢?
LoggingSystem的加载
主要参与类说下:
LoggingApplicationListener
ApplicationStartingEvent
LogbackLoggingSystem
LoggingSystem
不多说,先上图
1、应用使用SpringApplication.run(Chapter1Application.class, args);
启动
2、发送启动事件ApplicationStartingEvent
、ApplicationEnvironmentPreparedEvent
等
3、LoggingApplicationListener
接收事件进行事件分发
4、LoggingApplicationListener
接收事件ApplicationStartingEvent
5、LoggingApplicationListener
调用内部onApplicationStartingEvent(ApplicationStartingEvent event)
方法,使用LoggingSystem.get(classloader)初始化LoggingSystem。
6、然后调用LogbackLoggingSystem.beforeInitialize()
(因为这里我们用的是logback)
7、LoggingApplicationListener
接收事件ApplicationEnvironmentPreparedEvent
8、LoggingApplicationListener
调用内部onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event)
方法
9、onApplicationEnvironmentPreparedEvent
调用initialize(ConfigurableEnvironment environment, ClassLoader classLoader)
方法,会根据环境变量配置进行logging system的初始化
LoggersEndpoint
最后,我们就来看看LoggersEndpoint就可以了,这里为了方便解读我就不全拷贝过来了,需要哪里选哪里
首先看我们对日志操作的三个接口:
GET /actuator/loggers
对应public Map<String, Object> loggers()
方法GET /actuator/loggers/{name}
对应public LoggerLevels loggerLevels(@Selector String name)
方法POST /actuator/loggers/{name}
对应public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel)
方法
这里拿一个public Map<String, Object> loggers()
方法举例说明,其他的差不多
@ReadOperation
public LoggerLevels loggerLevels(@Selector String name) {
Assert.notNull(name, "Name must not be null");
LoggerGroup group = this.loggerGroups.get(name);
if (group != null) {
return new GroupLoggerLevels(group.getConfiguredLevel(), group.getMembers());
}
LoggerConfiguration configuration = this.loggingSystem.getLoggerConfiguration(name);
return (configuration != null) ? new SingleLoggerLevels(configuration) : null;
}
代码其实很简单,就不做过多解读了。
四、絮叨
其实日志在我们的系统应用中很重要,对于问题的排查也是重要的凭证。根据经验我们系统的日志最好能做到几个点:
日志格式统一化
最好提供统一的日志组件,比如我们就使用了公共的logback-spring.xml组件。如果我们需要修改日志某些特性,比如加APM日志等等,只需要改一个点,我们系统的所以日志状态都会发生改变。
最好使用动态配置,调整日志配置,比如Apollo
世界上最快的捷径,就是脚踏实地,本文已收录架构技术专栏关注这个喜欢分享的地方。
开源项目:
- 分布式监控(Gitee GVP最有价值开源项目 ):https://gitee.com/sanjiankethree/cubic
- 摄像头视频流采集:https://gitee.com/sanjiankethree/cubic-video
Spring Boot 系列:日志动态配置详解的更多相关文章
- Spring Boot 2.0 教程 - 配置详解
Spring Boot 可以通过properties文件,YAML文件,环境变量和命令行参数进行配置.属性值可以通过,@Value注解,Environment或者ConfigurationProper ...
- spring boot slf4j日记记录配置详解
https://blog.csdn.net/liuweixiao520/article/details/78900779
- Spring Boot的每个模块包详解
Spring Boot的每个模块包详解,具体如下: 1.spring-boot-starter 这是Spring Boot的核心启动器,包含了自动配置.日志和YAML. 2.spring-boot-s ...
- Spring Boot源码中模块详解
Spring Boot源码中模块详解 一.源码 spring boot2.1版本源码地址:https://github.com/spring-projects/spring-boot/tree/2.1 ...
- spring boot 配置文件properties和YAML详解
spring boot 配置文件properties和YAML详解 properties中配置信息并获取值. 1:在application.properties配置文件中添加: 根据提示创建直接创建. ...
- java log4j基本配置及日志级别配置详解
java log4j日志级别配置详解 1.1 前言 说出来真是丢脸,最近被公司派到客户公司面试外包开发岗位,本来准备了什么redis.rabbitMQ.SSM框架的相关面试题以及自己做过的一些项目回顾 ...
- 【Spring】——声明式事务配置详解
项目中用到了spring的事务: @Transactional(rollbackFor = Exception.class, transactionManager = "zebraTrans ...
- Spring Boot系列——日志配置
日志,通常不会在需求阶段作为一个功能单独提出来,也不会在产品方案中看到它的细节.但是,这丝毫不影响它在任何一个系统中的重要的地位. 为了保证服务的高可用,发现问题一定要即使,解决问题一定要迅速,所以生 ...
- Spring Boot默认日志logback配置解析
前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候,是带着下面几个问题来查资料的,你呢 如何引入日志? 日志输出格式以及输出方式如何配置? 代码中如何使用? 正文 Sp ...
随机推荐
- linux vi编辑
编辑模式 使用vi进入文本后,按i开始编辑文本 退出编辑模式 按ESC键,然后: 退出vi :q! 不保存文件,强制退出vi命令 :w 保存文件,不退出vi命令 :wq 保存文件,退出vi命令 中断v ...
- mobiscroll
https://docs.mobiscroll.com/3-2-3/jquery/calendar#!options
- 虚拟机安装centos常见问题
一.centos下载安装 环境:win10系统,虚拟机vm12, centos6.5 http://vault.centos.org/ 链接打开 选择6.5=>isos/=>x86_64= ...
- odoo提示你没有查看此类文档的权限
问题: odoo出现提示信息:"抱歉, 你没有访问此类型文档的权限 '未知' (_unknown). 没有为此操作指定权限组 - (操作: read, 用户: 2)" 出错原因: ...
- 记tp5.1使用composer PhpOffice的xlsx表格文件导入数据库
在项目环境下composer require phpoffice/phpspreadsheet在项目中引用use PhpOffice\PhpSpreadsheet\IOFactory; 下面是 上传x ...
- [记录点滴]授人以渔,从Tensorflow找不到dll扩展到如何排查问题
[记录点滴]授人以渔,从Tensorflow找不到dll扩展到如何排查问题 目录 [记录点滴]授人以渔,从Tensorflow找不到dll扩展到如何排查问题 0x00 摘要 0x01 引言 0x02 ...
- Fork Join 并发任务执行框架
Fork Join 体现了分而治之 什么是分而治之? 规模为N的问题,如果N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原 ...
- 《Head First 设计模式》:状态模式
正文 一.定义 状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类. 要点: 状态模式允许一个对象基于内部状态而拥有不同的行为. 状态模式将状态封装成为独立的类,并将动作委托到代 ...
- Volatile禁止指令重排序(三)
Volatile禁止指令重排 计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种: 源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系 ...
- Spring循环依赖的三种方式
引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一 ...