java中的日志有哪些级别_Java日志:日志级别动态调整

本文介绍了如何在SpringBoot应用中利用SpringBoot Actuator组件动态修改日志级别,包括通过HTTP端点和Arthas OGNL命令进行配置,以及针对不同日志框架(如Logback、Log4j2)的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

将日志级别设低,例如 DEBUG ;

重启应用;

复现问题,观察日志;

实际上是可以动态修改日志级别,无需重启应用,立即生效。本文收集了3种动态修改日志级别的文章,分别是

Spirng Boot动态修改日志级别

从 Spring Boot 1.5 开始,Spring Boot Actuator 组件就已提供动态修改日志级别的能力。

示例

1.引入spring-boot-starter-actuator依赖,内容如下:

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-actuator

复制代码

2.编写测试代码,如下:

@SpringBootApplication

public class DemoApplication{

public static void main(String[] args){

SpringApplication.run(DemoApplication.class, args);

}

}

@RestController

public class DemoController{

private static Logger logger = LoggerFactory.getLogger(DemoController.class);

@GetMapping("/helloworld")

public String helloworld(){

logger.debug("welcome to learn spring boot");

return "welcome to learn spring boot";

}

}

复制代码

3.配置文件

management:

endpoints:

web:

exposure:

include: 'loggers'

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

测试

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

原理

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);

}

// ...其他省略

}

复制代码其中, Endpoint 、WriteOperation 、@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 方法:送POST请求后,name就是我们传的包名或者类名,configuredLevel就是我们传的消息体。

org.springframework.boot.logging.LoggingSystem#setLogLevel是抽象方法,具体实现由子类完成。LoggingSystem类结构如下图所示:

65d6c59a94ef36e9515797e1058e28ad.png

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

public abstract class LoggingSystem{

private static final Map SYSTEMS;

static {

Map 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。

Arthas ognl命令动态修改日志级别

使用ognl命令可以动态修改日志级别,步骤如下:

查找当前类的classLoaderHash

e1b7f3e145a424c36abe87bbe9e10a86.png

用OGNL获取logger

ef678a53e812d17de3b90cf5594e0d05.png

可以发现日志使用的是Logback框架。

单独设置DemoController的logger level

0477d651933e1d7d46ba6667f735b0ef.png

全局设置logger level

481bc277f12302661e1304719c1970f7.png

如果使用的日志框架是log4j,则使用上述ognl命令则会报错。至于为什么?请阅读Java日志:SLF4J详解

美团日志级别动态调整小工具

初始化:确定所使用的日志框架,获取配置文件中所有的Logger内存实例,并将它们的引用缓存到Map容器中。

String type = StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr();

if (LogConstant.LOG4J_LOGGER_FACTORY.equals(type)) {

logFrameworkType = LogFrameworkType.LOG4J;

Enumeration enumeration = org.apache.log4j.LogManager.getCurrentLoggers();

while (enumeration.hasMoreElements()) {

org.apache.log4j.Logger logger = (org.apache.log4j.Logger) enumeration.nextElement();

if (logger.getLevel() != null) {

loggerMap.put(logger.getName(), logger);

}

}

org.apache.log4j.Logger rootLogger = org.apache.log4j.LogManager.getRootLogger();

loggerMap.put(rootLogger.getName(), rootLogger);

} else if (LogConstant.LOGBACK_LOGGER_FACTORY.equals(type)) {

logFrameworkType = LogFrameworkType.LOGBACK;

ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory();

for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {

if (logger.getLevel() != null) {

loggerMap.put(logger.getName(), logger);

}

}

ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

loggerMap.put(rootLogger.getName(), rootLogger);

} else if (LogConstant.LOG4J2_LOGGER_FACTORY.equals(type)) {

logFrameworkType = LogFrameworkType.LOG4J2;

org.apache.logging.log4j.core.LoggerContext loggerContext = (org.apache.logging.log4j.core.LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);

Map map = loggerContext.getConfiguration().getLoggers();

for (org.apache.logging.log4j.core.config.LoggerConfig loggerConfig : map.values()) {

String key = loggerConfig.getName();

if (StringUtils.isBlank(key)) {

key = "root";

}

loggerMap.put(key, loggerConfig);

}

} else {

logFrameworkType = LogFrameworkType.UNKNOWN;

LOG.error("Log框架无法识别: type={}", type);

}

复制代码

获取Logger列表:从本地Map容器取出。

private String getLoggerList(){

JSONObject result = new JSONObject();

result.put("logFramework", logFrameworkType);

JSONArray loggerList = new JSONArray();

for (ConcurrentMap.Entry entry : loggerMap.entrySet()) {

JSONObject loggerJSON = new JSONObject();

loggerJSON.put("loggerName", entry.getKey());

if (logFrameworkType == LogFrameworkType.LOG4J) {

org.apache.log4j.Logger targetLogger = (org.apache.log4j.Logger) entry.getValue();

loggerJSON.put("logLevel", targetLogger.getLevel().toString());

} else if (logFrameworkType == LogFrameworkType.LOGBACK) {

ch.qos.logback.classic.Logger targetLogger = (ch.qos.logback.classic.Logger) entry.getValue();

loggerJSON.put("logLevel", targetLogger.getLevel().toString());

} else if (logFrameworkType == LogFrameworkType.LOG4J2) {

org.apache.logging.log4j.core.config.LoggerConfig targetLogger = (org.apache.logging.log4j.core.config.LoggerConfig) entry.getValue();

loggerJSON.put("logLevel", targetLogger.getLevel().toString());

} else {

loggerJSON.put("logLevel", "Logger的类型未知,无法处理!");

}

loggerList.add(loggerJSON);

}

result.put("loggerList", loggerList);

LOG.info("getLoggerList: result={}", result.toString());

return result.toString();

}

复制代码

修改Logger的级别

private String setLogLevel(JSONArray data){

LOG.info("setLogLevel: data={}", data);

List loggerList = parseJsonData(data);

if (CollectionUtils.isEmpty(loggerList)) {

return "";

}

for (LoggerBean loggerbean : loggerList) {

Object logger = loggerMap.get(loggerbean.getName());

if (logger == null) {

throw new RuntimeException("需要修改日志级别的Logger不存在");

}

if (logFrameworkType == LogFrameworkType.LOG4J) {

org.apache.log4j.Logger targetLogger = (org.apache.log4j.Logger) logger;

org.apache.log4j.Level targetLevel = org.apache.log4j.Level.toLevel(loggerbean.getLevel());

targetLogger.setLevel(targetLevel);

} else if (logFrameworkType == LogFrameworkType.LOGBACK) {

ch.qos.logback.classic.Logger targetLogger = (ch.qos.logback.classic.Logger) logger;

ch.qos.logback.classic.Level targetLevel = ch.qos.logback.classic.Level.toLevel(loggerbean.getLevel());

targetLogger.setLevel(targetLevel);

} else if (logFrameworkType == LogFrameworkType.LOG4J2) {

org.apache.logging.log4j.core.config.LoggerConfig loggerConfig = (org.apache.logging.log4j.core.config.LoggerConfig) logger;

org.apache.logging.log4j.Level targetLevel = org.apache.logging.log4j.Level.toLevel(loggerbean.getLevel());

loggerConfig.setLevel(targetLevel);

org.apache.logging.log4j.core.LoggerContext ctx = (org.apache.logging.log4j.core.LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);

ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig.

} else {

throw new RuntimeException("Logger的类型未知,无法处理!");

}

}

return "success";

}

复制代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值