Spring Boot 2动态修改日志级别

栏目: Java · 发布时间: 4年前

内容简介:本文基于:Spring Boot 2.1.3,理论支持Spring Boot 2.x所有版本。作为程序猿,定位问题是我们的日常工作,而日志是我们定位问题非常重要的依据。传统方式定位问题时,往往是如下步骤:

本文基于:Spring Boot 2.1.3,理论支持Spring Boot 2.x所有版本。

作为程序猿,定位问题是我们的日常工作,而日志是我们定位问题非常重要的依据。传统方式定位问题时,往往是如下步骤:

将日志级别设低,例如  DEBUG 重启应用; 复现问题,观察日志;

如果能动态修改日志级别(无需重启应用,就能立刻刷新),那绝对 如猫添翼 。事实上,从  Spring Boot 1.5 开始, Spring Boot Actuator 组件就已提供动态修改日志级别的能力。

TIPS

其实更低版本也只需简单扩展,即可实现动态修改日志级别。 对Spring Boot Actuator感到陌生的童鞋,可先前往  Spring Boot Actuator [1]  了解基础用法。

废话不多说了,亮代码吧。

编码

1 加依赖


 

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

这里的 spring-boot-starter-web 不是必须的,只是下面测试代码要用到。

2 写代码


 

package com.itmuch.logging;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;



/**

* @author itmuch.com

*/

@RestController

public class TestController {

private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);


@GetMapping("/test")

public String simple() {

LOGGER.debug("这是一个debug日志...");

return "test";

}

}


3 写配置:


 

management:

endpoints:

web:

exposure:

include: 'loggers'

由于Spring Boot 2.x默认只暴露 /health 以及  /info 端点,而日志控制需要用到  /loggers 端点,故而需要设置将其暴露。

代码编写完成啦。

测试

/loggers 端点提供了  查看 以及  修改 日志级别的能力。

测试1:查看当前应用各包/类的日志级别

访问 http://localhost:8080/actuator/loggers ,可看到类似如下的结果:


 

{

"levels": ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"],

"loggers": {

"ROOT": {

"configuredLevel": "INFO",

"effectiveLevel": "INFO"

},

"com.itmuch.logging.TestController": {

"configuredLevel": null,

"effectiveLevel": "INFO"

}

}

// ...省略

}

测试2:查看指定包/类日志详情

访问 http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController ,可看到类似如下的结果:

{"configuredLevel":null,"effectiveLevel":"INFO"}

由测试不难发现,想看哪个包/类的日志,只需构造 /actuator/loggers/包名类名全路径 去访问即可。

测试3:修改日志级别

TestController 类中,笔者编写设置了一条日志  LOGGER.debug("这是一个debug日志..."); ,而由测试1,默认的日志级别是INFO,所以不会打印。下面来尝试将该类的日志级别设为DEBUG。


 

curl -X POST http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController \

-H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8" \

--data '{"configuredLevel":"debug"}'

如上,只需发送一个POST请求,并将请求body设为: {"configuredLevel":"debug"} 即可。

此时,访问 localhost:8080/test 会看到类似如下的日志:

2019-03-28 16:24:04.513 DEBUG 19635 --- [nio-8080-exec-7] com.itmuch.logging.TestController        : 这是一个debug日志...

并且,此时再访问 http://localhost:8080/actuator/loggers/com.itmuch.logging.TestController ,可看到类似如下的结果:

{"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}

说明已成功动态修改日志级别。

原理分析

TIPS

本节着重分析如何实现动态修改。

Actuator有约定,  /actuator/xxx  端点的定义代码在  xxxEndpoint  中 。故而,找到类  org.springframework.boot.actuate.logging.LoggersEndpoint ,可看到类似如下的代码:


 

@Endpoint(id = "loggers")

public class LoggersEndpoint {

private final LoggingSystem loggingSystem;


@WriteOperation

public void configureLogLevel(@Selector String name,

@Nullable LogLevel configuredLevel) {

Assert.notNull(name, "Name must not be empty");

this.loggingSystem.setLogLevel(name, configuredLevel);

}

// ...其他省略

}

其中, EndpointWriteOperation@Selector 都是Spring Boot 2.0开始提供的新注解。

@Endpoint(id = "loggers") 用来描述 Spring Boot Actuator 的端点,这样就会产生一个 /actuator/loggers 的路径,它类似于Spring MVC的  @RequestMapping("loggers")

@WriteOperation 表示这是一个写操作,它类似于Spring MVC的  @PostMapping 。Spring Boot Actuator还提供了其他操作,如下表:

Operation HTTP method
@ReadOperation GET
@WriteOperation POST
@DeleteOperation DELETE

@Selector 用于筛选  @Endpoint 注解返回值的子集,它类似于Spring MVC的  @PathVariable

这样,上面的代码就很好理解了—— configureLogLevel 方法里面就一行代码 : this.loggingSystem.setLogLevel(name, configuredLevel); ,发送POST请求后,name就是我们传的包名或者类名,configuredLevel就是我们传的消息体。

怎么实现动态修改的呢?不妨点进去看看,然后发现代码如下:


 

// org.springframework.boot.logging.LoggingSystem#setLogLevel

public void setLogLevel(String loggerName, LogLevel level) {

throw new UnsupportedOperationException("Unable to set log level");

}

嘿嘿,没事,肯定有实现类, 该方法在如下实现类被实现:


 

# 适用于java.util.loggingLoggingSystem

org.springframework.boot.logging.java.JavaLoggingSystem

# 适用于Log4j 2的LoggingSystem

org.springframework.boot.logging.log4j2.Log4J2LoggingSystem

# 适用于logbackLoggingSystem

org.springframework.boot.logging.logback.LogbackLoggingSystem

# 啥都不干的LoggingSystem

org.springframework.boot.logging.LoggingSystem.NoOpLoggingSystem

Spring Boot 2.x中,默认使用Logback,因此进入到 LogbackLoggingSystem 中,代码如下:


 

@Override

public void setLogLevel(String loggerName, LogLevel level) {

ch.qos.logback.classic.Logger logger = getLogger(loggerName);

if (logger != null) {

logger.setLevel(LEVELS.convertSystemToNative(level));

}

}

至此,就真相大白了。其实根本没有黑科技,Spring Boot本质上还是使用了Logback的API, ch.qos.logback.classic.Logger.setLevel 实现日志级别的修改。

你可能会好奇

你可能会好奇,LoggingSystem有这么多实现类,Spring Boot怎么知道什么情况下用什么LoggingSystem呢?可在 org.springframework.boot.logging.LoggingSystem 找到类似如下代码:


 

public abstract class LoggingSystem {

private static final Map<String, String> SYSTEMS;


static {

Map<String, String> systems = new LinkedHashMap<>();

systems.put("ch.qos.logback.core.Appender",

"org.springframework.boot.logging.logback.LogbackLoggingSystem");

systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",

"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");

systems.put("java.util.logging.LogManager",

"org.springframework.boot.logging.java.JavaLoggingSystem");

SYSTEMS = Collections.unmodifiableMap(systems);

}


/**

* Detect and return the logging system in use. Supports Logback and Java Logging.

* @param classLoader the classloader

* @return the logging system

*/

public static LoggingSystem get(ClassLoader classLoader) {

String loggingSystem = System.getProperty(SYSTEM_PROPERTY);

if (StringUtils.hasLength(loggingSystem)) {

if (NONE.equals(loggingSystem)) {

return new NoOpLoggingSystem();

}

return get(classLoader, loggingSystem);

}

return SYSTEMS.entrySet().stream()

.filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))

.map((entry) -> get(classLoader, entry.getValue())).findFirst()

.orElseThrow(() -> new IllegalStateException(

"No suitable logging system located"));

}

// 省略不相关内容...

}

由代码不难发现,其实就是构建了一个名为 SYSTEMS 的map,作为各种日志系统的字典;然后在  get 方法中,看应用是否加载了map中的类;如果加载了,就通过反射,初始化响应  LoggingSystem 。例如:Spring Boot发现当前应用加载了 ch.qos.logback.core.Appender ,就去实例化  org.springframework.boot.logging.logback.LogbackLoggingSystem

界面

本文是使用 curl 手动发送  POST 请求手动修改日志级别的, 该方式不适用生产,因为很麻烦,容易出错 。生产环境,建议根据Actuator提供的RESTful API定制界面,或使用  Spring Boot Admin ,可视化修改日志级别,如下图所示:

Spring Boot 2动态修改日志级别

想修改哪个包/类的日志级别,直接点击即可。

配套代码

GitHub [2] Gitee [3]

References

[1] Spring Boot Actuator:  http://www.itmuch.com/spring-cloud/finchley-3/

[2] GitHub:  https://github.com/eacdy/spring-boot-study/tree/master/spring-boot-logging-change-logging-level

[3] Gitee:  https://gitee.com/itmuch/spring-boot-study/tree/master/spring-boot-logging-change-logging-level

Spring Boot 2动态修改日志级别

长按可以订阅

喜欢就点个在看

Spring Boot 2动态修改日志级别


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Building Social Web Applications

Building Social Web Applications

Gavin Bell / O'Reilly Media / 2009-10-1 / USD 34.99

Building a social web application that attracts and retains regular visitors, and gets them to interact, isn't easy to do. This book walks you through the tough questions you'll face if you're to crea......一起来看看 《Building Social Web Applications》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码