文档章节

Spring Boot 2动态修改日志级别

周立_ITMuch
 周立_ITMuch
发布于 04/22 10:02
字数 1563
阅读 1220
收藏 5

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

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

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

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

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

TIPS

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

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

编码

  • 加依赖

    <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 不是必须的,只是下面测试代码要用到。

  • 写代码

    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";
        }
    }
    
  • 写配置:

    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.logging的LoggingSystem
org.springframework.boot.logging.java.JavaLoggingSystem
# 适用于Log4j 2的LoggingSystem
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
# 适用于logback的LoggingSystem
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-admin-loggers

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

配套代码

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

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

原文首发

http://www.itmuch.com/spring-boot/change-logger-level/

关注我

img

© 著作权归作者所有

周立_ITMuch

周立_ITMuch

粉丝 200
博文 111
码字总数 110927
作品 2
南京
私信 提问
Spring Boot 动态修改日志级别

SpringBoot1.5.X版本引入的一个新的控制端点:/loggers,该端点将为我们提供动态修改Spring Boot应用日志级别的强大功能。该功能的使用非常简单,它依然延续了Spring Boot自动化配置的实现,...

阿刚ABC
2018/01/25
202
0
在spring cloud中使用springboot admin

maven 这里由于spring boot版本不一致,因此eclude了admin server中的start web spring-boot-admin-server-ui-activiti没看出来有什么用,因此注释掉了 由于使用了turbine来聚合hystrix,因此...

go4it
2017/03/28
246
1
spring boot框架学习4-spring boot核心(3)

本节主要: 1:spring boot 为我们提供的 starter pom 都有哪些 2:怎么添加xml配置文件 3:日志相关 本文是《凯哥陪你学系列-框架学习之spring boot框架学习》中第四篇 spring boot框架学习...

中凯_凯哥java
2017/10/31
351
0
SpringBoot实用小技巧之动态设置SpringBoot日志级别

  有时线上问题我们用打日志的方式来观察错误或埋点参数,但由于这些日志如果都打出来会占用大量存储空间而且覆盖了一些有效信息,所以线上级别一般设置INFO,调试级别用作特殊情况下。此时...

james8888
04/15
0
0
Spring Boot开发之流水无情(二)

上篇散仙写了一个很简单的入门级的Spring Boot的例子,没啥技术含量,不过,其实学任何东西只要找到第一个突破口,接下来的事情就好办了,人最怕什么? 我想莫过于干一件事情,没有下手的地方...

九劫散仙
2015/04/26
63.2K
22

没有更多内容

加载失败,请刷新页面

加载更多

oracle查杀连接会话

由于频繁强制启停tomcat不清理连接等情况时可能导致oracle连接爆满,此时可以使用这个方式清理 --查询select sess.sid,sess.serial#,sess.machine,lo.oracle_username,lo.os_user_name,...

孑竹三秋
17分钟前
5
0
为什么互联网公司天天都在招人?

互联网公司招聘是很重要的环节,互联网公司离职率普遍较高,传统企业离职率较低,所以对于公司招聘是很重要的环节,同样一句“很重要”我看到许多人理解其程度实际上大相径庭。在很多互联网公...

码农突围
18分钟前
5
0
001-open-falcon的单机版安装

open-falcon 每台服务器,都有安装falcon-agent,falcon-agent是一个golang开发的daemon程序,用于自发现的采集单机的各种数据和指标 单机安装 redis mkdir /home/redis && cd /home/redis...

伟大源于勇敢的开始
19分钟前
3
0
人工智能领跑的未来,智能CRM未来可期

现在,几乎每个技术预测故事都以同样的方式开始:人工智能AI正在开辟新的可能性。这种趋势同样发生在CRM领域中。 人工智能正在通过访问和分析来改变CRM。它正在通过添加语音助手、同时改善工...

怡海软件-CRM
21分钟前
3
0
mysql-5.7.28-linux-glibc2.12-x86_64配置(参考)

[client]socket                                            = /data/mysql/var/mysql.sockport                                         ...

Wybaron
25分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部