<返回更多

Springboot 动态改变Log级别

2020-07-09    
加入收藏

背景

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

那么问题就来了,可不可以动态修改日志级别呢?(无需重启应用,就能立刻刷新)

答案是肯定的!

下面提供几个思路给大家参考。

使用 LoggingSystem 自行开发修改日志级别的接口

不废话,直接上代码

@Resource
        private LoggingSystem loggingSystem;

@PostMApping("/changeLogLevel")
public void changeLogLevel(@RequestParam("name") String name, @RequestParam("level") String level) {
  LogLevel logLevel = LogLevel.valueOf(level.toUpperCase());
  loggingSystem.setLogLevel(name, logLevel);
}

what?这么简单?是的,就是这么简单。

LoggingSystem 这个抽象类就是关键,其实后面所要介绍的几个修改思路(actuator,Apollo,mq)的底层也是基于它进行修改的。

如果大家对LoggingSystem这个类在底层究竟是如何实现动态修改日志级别感兴趣的话,请评论区留言,我抽时间再写一篇文章来详细说一下。

然后再说一下这种方式的优缺点吧。

优点:简单!

缺点:也很明显,只适合单机/生产机器不多的服务。如果你的服务有上百个节点,用这种方式来修改。。。

那有朋友会问,有没有适合多机集群的服务的修改方式?

那必须有啊,下面介绍一下思路二。

使用 Apollo + LoggingSystem

这种方式的前提是系统接入了Apollo。

也不废话,直接上代码吧。代码里也有注释。

@Configuration
public class LogLevelRefresher {
    private final static Logger log = LoggerFactory.getLogger(com.dylan.config.LoggingLevelRefresher.class);

    private static final String PREFIX = "logging.level.";
    private static final String ROOT = LoggingSystem.ROOT_LOGGER_NAME;

    @Resource
    private LoggingSystem loggingSystem;


    /**
     * 支持类配置
     */
    @PostConstruct
    private void init() {
      //要修改日志级别的key(包路径/类路径)
        String keyStr = ConfigCenterService.getAppProperty("log.changeKey", "logging.level.root,logging.level.com.dylan.config");
        Set<String> changedKeys = Arrays.stream(keyStr.split(",")).collect(Collectors.toSet());
        refreshLoggingLevels(changedKeys);
    }

    /**
    * 修改Apollo配置后的回调方法
    */
    @ApolloConfigChangeListener
    private void onChange(ConfigChangeEvent changeEvent) {
        refreshLoggingLevels(changeEvent.changedKeys());
    }

    private void refreshLoggingLevels(Set<String> changedKeys) {
        for (String key : changedKeys) {
            // key may be : logging.level.com.example.web
            if (StringUtils.startsWithIgnoreCase(key, PREFIX)) {
                String loggerName = PREFIX.equalsIgnoreCase(key) ? ROOT : key.substring(PREFIX.length());
                String strLevel = ConfigCenterService.getProperty(key, parentStrLevel(loggerName));
                LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
                loggingSystem.setLogLevel(loggerName, level);
              //打印一下信息,可以不用
                log(loggerName, strLevel);
            }
        }
    }

    private String parentStrLevel(String loggerName) {
        String parentLoggerName = loggerName.contains(".") ? loggerName.substring(0, loggerName.lastIndexOf(".") : ROOT;
        return loggingSystem.getLoggerConfiguration(parentLoggerName).getEffectiveLevel().name();
    }

    /**
     * 获取当前类的Logger对象有效日志级别对应的方法,进行日志输出。举例:
     * 如果当前类的EffectiveLevel为WARN,则获取的Method为 `org.slf4j.Logger#warn(JAVA.lang.String, java.lang.Object, java.lang.Object)`
     * 目的是为了输出`changed {} log level to:{}`这一行日志
     */
    private void log(String loggerName, String strLevel) {
        try {
            LoggerConfiguration loggerConfiguration = loggingSystem.getLoggerConfiguration(log.getName());
            Method method = log.getClass().getMethod(loggerConfiguration.getEffectiveLevel().name().toLowerCase(), String.class, Object.class, Object.class);
            method.invoke(log, "changed {} log level to:{}", loggerName, strLevel);
        } catch (Exception e) {
            log.error("changed {} log level to:{} error", loggerName, strLevel, e);
        }
    }
}

大家可以看到,Apollo的方式最终也是LoggingSystem 这个类进行修改日志级别的操作。

那可能大家会问,Apollo在这里的作用是什么?

如果大家用过Apollo的话就会发现,在Apollo可视化管理系统中,每个系统都有一个实例列表,里面就是我们具体的应用地址。所以在这里你可以认为Apollo有类似注册中心的作用,在我们应用启动的时候,Apollo后台就会记录下来。

所以Apollo能实现集群的日志级别动态修改的原理就在这。是不是也很简单呢?

使用 MQ + LoggingSystem

 

如果你们的系统没有接入Apollo的话,那应该如何实现集群的日志级别动态修改呢?

MQ就是其中一个选择。我简单说一下实现思路吧,具体实现也很简单,就留给大家去动手实践啦。

  1. 暴露一个修改日志级别的接口
  2. 这个接口要做的是使用producer来发送一个广播类型的MQ,注意了,是广播类型的
  3. 在consumer里面通过LoggingSystem进行日志级别的修改即可。

是不是很简单呢?

使用Springboot的 actuator 组件

其实这种方法和方式一是差不多的,只是actuator把接口通过端点Endpoints 的方式暴露出来。

至于什么是端点(Endpoints),我简单介绍一下吧。

Endpoints 是 Actuator 的核心部分,它用来监视应用程序及交互,spring-boot-actuator中已经内置了非常多的Endpoints(health、info、beans、httptrace、shutdown等等),同时也允许我们扩展自己的端点。

Endpoints 分成两类:原生端点和用户自定义端点;自定义端点主要是指扩展性,用户可以根据自己的实际应用,定义一些比较关心的指标,在运行期进行监控。

原生端点是在应用程序里提供的众多 restful api 接口,通过它们可以监控应用程序运行时的内部状况。

原生端点又可以分成三类:

  1. 应用配置类:可以查看应用在运行期间的静态信息:例如自动配置信息、加载的spring bean信息、yml文件配置信息、环境信息、请求映射信息;
  2. 度量指标类:主要是运行期间的动态信息,例如堆栈、请求链、一些健康指标、metrics信息等
  3. 操作控制类:主要是指shutdown,用户可以发送一个请求将应用的监控功能关闭。

我们这里修改配置文件用到的就是应用配置类的端点。

查看当前应用各包/类的日志级别

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"
		}
	}
	// ...省略
}

查看指定包/类日志详情

http://localhost:8080/actuator/loggers/com.dylan.logging.TestController

可看到类似如下的结果:

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

修改日志级别

POST方式,json格式的参数

example:http://localhost:8080/actuator/loggers/com.dylan.controller.IncreaseAgentController

Springboot 动态改变Log级别

actuator修改日志级别

但这种方式和方式一有同样的局限性,就是只适合单机或者开发环境。如果想用这种方式的话可以接入Spring Boot Admin。通过后台的方式进行管理。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>