动态修改springboot日志级别

一、背景

为了减少日志频繁打印带来的性能影响,线上设置的日志级别相对较高。当线上应用出现问题需要我们排查的时候,可能需要适当降低日志级别(例如DEBUG)来打印更多的日志信息。

传统的方式需要1、修改日志级别 2、重启应用 3、问题复现排查问题。这个过程需要重启应用,比较麻烦,效率较低。考虑某一种方式能不重启应用的情况下能够动态修改日志级别。

二、方案

spring boot1.5开始,spring actuator开始支持对日志级别的动态修改。

1、基础配置

actuator 暴露的http接口只有info和health,需要把loggers端点配置上才能通过调用http接口使用日志功能。

动态修改springboot日志级别

2、问题出现

设置日志级别不生效。

调用接口查询和设置日志级别都没有报错。设置新的日志级别后再查询也是设置后的级别。但是控制台看并没有生效,还是打印启动时设置的日志级别及以上的日志信息。

PS:上面提到的日志设置级别没有生效的原因,是因为现在的产品应用中都是集成的log4j日志框架。这个日志框架比较老,actuator接口已经不支持设置log4j日志级别了。我调用设置级别的接口不报错,是因为是设置级别最终落到了默认的JavaLoggingSystem这个日志框架上了。但是代码中@Slf4j绑定的日志框架是log4j。所以没有生效。

我们的项目中exclude了spring boot默认支持的logback而选用了老版本的log4j。

3、通过源码分析问题原因

LoggersEndpoint.java

这个loggers()就是对外提供的查询日志的接口。我们看到是通过this.loggingSystem这个对象的操作完成日志的查询包括以后的配置。

然后看一下这个LoggingSystem这个抽象类。

动态修改springboot日志级别

在应用启动时会调用LoggingSystem中的public static LoggingSystem get(ClassLoader classLoader)方法。这个方法会选择当前应用正在使用的日志实现框架。

动态修改springboot日志级别

我们可以看到,上图看到,会从SYSTEMS这个Map中筛选出当前应用的日志实现框架。当前支持的框架有三个:logback, log4j2LoggingSystem, JavaLoggingSystem。如果没有匹配到配置的日志框架,会默认选中JavaLoggingSystem。

动态修改springboot日志级别

下面的logging包中也可以看到LoggingSystem支持的是java,log4j2和loback这三个日志实现框架

动态修改springboot日志级别

4、方案

集成log4j2框架

下面我就替换成了log4j2日志框架。

compile 'org.springframework.boot:spring-boot-starter-log4j2'

同时exclude下面关于log4j的组件, 否则log4j和log4j2共存的情况下,会默认选择log4j。

all*.exclude group: 'org.slf4j', module: 'slf4j-log4j12'

all*.exclude group: 'log4j', module: 'log4j'

log4j2配置文件模板

为了和原来log4j的打印日志效果相同,在RollingFile标签中配置输出日志文件的名称,格式和滚动时间间隔。另外在<logger>标签中指定com.XX.XX包。

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE configuration>  
  3. <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->  
  4. <!--monitorIntervalLog4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->  
  5. <configuration monitorInterval="5">  
  6.     <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->  
  7.   
  8.     <!--变量配置-->  
  9.     <Properties>  
  10.         <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->  
  11.         <!-- %logger{36} 表示 Logger 名字最长36个字符 -->  
  12.         <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />  
  13.         <!-- 定义日志存储的路径 -->  
  14.         <property name="FILE_PATH" value="log" />  
  15.         <property name="FILE_NAME" value="ebs" />  
  16.         <property name="PRODUCT_NAME" value="ebs" />  
  17.     </Properties>  
  18.   
  19.     <appenders>  
  20.   
  21.         <console name="Console" target="SYSTEM_OUT">  
  22.             <!--输出日志的格式-->  
  23.             <PatternLayout pattern="${LOG_PATTERN}"/>  
  24.             <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch-->  
  25.             <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>  
  26.         </console>  
  27.   
  28. <!--        <!–文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用>  
  29.         <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">  
  30.             <PatternLayout pattern="${LOG_PATTERN}"/>  
  31.         </File>-->  
  32.   
  33.         <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->  
  34.         <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${PRODUCT_NAME}/audit.log" filePattern="${FILE_PATH}/${PRODUCT_NAME}/audit-%d{yyyy-MM-dd-HH}.log">  
  35.             <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch-->  
  36.             <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>  
  37.             <PatternLayout pattern="${LOG_PATTERN}"/>  
  38.             <Policies>  
  39.                 <!--interval属性用来指定多久滚动一次,默认是1 hour-->  
  40.                 <TimeBasedTriggeringPolicy modulate="true" interval="1"/>  
  41.                 <!--<SizeBasedTriggeringPolicy size="10MB"/>-->  
  42.             </Policies>  
  43.             <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->  
  44.             <DefaultRolloverStrategy max="15"/>  
  45.         </RollingFile>  
  46.   
  47.     </appenders>  
  48.   
  49.     <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->  
  50.     <!--然后定义loggers,只有定义了logger并引入的appenderappender才会生效-->  
  51.     <loggers>  
  52.   
  53.         <!--过滤掉springmybatis的一些无用的DEBUG信息-->  
  54.         <logger name="org.hibernate" level="info" additivity="false">  
  55.             <AppenderRef ref="Console"/>  
  56.         </logger>  
  57.         <!--监控系统信息-->  
  58.         <!--若是additivity设为false,则 Logger 只会在自己的appender里输出,而不会在 Logger appender里输出。-->  
  59.         <Logger name="org.springframework" level="info" additivity="false">  
  60.             <AppenderRef ref="Console"/>  
  61.         </Logger>  
  62.   
  63.         <Logger name="com.XX.XX" level="info" additivity="false">  
  64.             <AppenderRef ref="RollingFileInfo" />  
  65.         </Logger>  
  66.   
  67.         <root level="info">  
  68.             <appender-ref ref="Console"/>  
  69.         </root>  
  70.     </loggers>  
  71.   
  72. </configuration>  

 

整个log4j2日志框架的结构如下:

门面:slf4j

桥接:  log4j-slf4j-impl

实现:log4j-api:log4j2定义的API, log4j-core:log4j2上述API的实现

三、测试接口

actuator提供两个接口分别用来查询和配置日志级别。

查询:get http://localhost:8080/loggers

设置:post http://localhost:8080/loggers/{name}

1、查询日志级别

get  http://localhost:8080/loggers

response

  1. {  
  2.     "levels": [  
  3.         "OFF",  
  4.         "FATAL",  
  5.         "ERROR",  
  6.         "WARN",  
  7.         "INFO",  
  8.         "DEBUG",  
  9.         "TRACE"  
  10.     ],  
  11.     "loggers": {  
  12.         "ROOT": {  
  13.             "configuredLevel": "INFO",  
  14.             "effectiveLevel": "INFO"  
  15.         },  
  16.         "com.XX.XX": {  
  17.             "configuredLevel": "WARN",  
  18.             "effectiveLevel": "WARN"  
  19.         },  
  20.         "org.hibernate": {  
  21.             "configuredLevel": "INFO",  
  22.             "effectiveLevel": "INFO"  
  23.         },  
  24.         "org.springframework": {  
  25.             "configuredLevel": "INFO",  
  26.             "effectiveLevel": "INFO"  
  27.         }  
  28.     }  
  29. }  

 

2、查询日志接口源码

动态修改springboot日志级别

3、设置日志级别接口

request:

post http://127.0.0.1:8081/ebs/actuator/loggers/{name}

body:

{

    "configuredLevel": "ERROR"

}

设置日志级别接口,在body中设置日志级别。

url中的name对应日志文件中的模块名称,例如ROOT, com.XX.XX

4、设置日志的接口代码

动态修改springboot日志级别

5、测试可行性

写一个接口,最基本的代码。如果打印信息符合设置的级别代表设置成功

动态修改springboot日志级别

level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.