乐趣区

关于java:如何动态改变日志级别

前言

对于日志级别,大部分我的项目可能都设置为 info 级别,当然也可能有一些谋求性能或者说蕴含很多敏感信息的我的项目间接将级别设置为 warn 或者 error;这时候如果我的项目中呈现一些未知异样,须要用到很具体的日志信息,此时如果我的项目中没有动静扭转日志级别的机制,排查问题将很辣手。

日志零碎

咱们罕用的一些日志零碎包含:Log4j2LogbackJava Util Logging;咱们想动静扭转日志的级别,前提是这些日志零碎都反对咱们间接设置日志等级,当然这些零碎提供了很简略的接口;

  • Log4j2
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
LoggerConfig loggerConfig = loggerContext.getConfiguration().getLoggers().get("root");
loggerConfig.setLevel(level);
  • Logback
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger logger = loggerContext.getLogger("root");
((ch.qos.logback.classic.Logger) logger).setLevel(level);
  • Java Util Logging
Logger logger = Logger.getLogger("root");
logger.setLevel(level);

当然除了下面间接设置日志级别的形式,也有能够动静加载配置文件的形式,同样也能够在配置文件中动静扭转日志级别,以 logback 为例:

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
File externalConfigFile = new File("logback.xml");
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();            
configurator.doConfigure(externalConfigFileLocation);

下面简略介绍了一下每种日志零碎都是如何去设置日志级别的,最要害的是设置完之后,能够实时失效,立马能够看到咱们想要的日志;有了这些上面其实就是通过何种形式去扭转日志级别的问题了;

如何动静扭转级别

如何去动静扭转级别,最简略的形式就是对外提供一个接口,给定一个日志级别作为参数实时变更;或者通过配置核心的形式;另外其实像 SpringBoot 这些支流的框架自身也提供了动静批改的性能;上面能够具体看一下是如何实现的,以 logback 为例;

自定义接口

自定义一个给定日志级别的接口,内部间接通过调用接口来扭转级别:

@RequestMapping(value = "logLevel/{logLevel}")
public String changeLogLevel(@PathVariable("logLevel") String logLevel) {
    try {LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger logger = loggerContext.getLogger("root");
        ((ch.qos.logback.classic.Logger) logger).setLevel(Level.valueOf(logLevel));
    } catch (Exception e) {logger.error("changeLogLevel error", e);
        return "fail";
    }
    return "success";
}

想要扭转日志级别间接申请如下地址即可,设置一个 debug 的级别:

http://[ip]:[port]/logLevel/debug

这种形式尽管比较简单,然而如果节点很多的话,操作起来就很麻烦,当然也能够汇总所有节点门路,一次操作触发所有节点的申请;其实最好的方法应该是相似公布订阅的形式,发布者会给所有订阅者都发送一个更改日志级别的告诉,有新的节点只有成为订阅者即可,这种形式其实就是当初支流的配置核心的形式。

配置核心

配置核心的目标其实就是把一些会常常变动的参数集中保存起来,某个系统启动时去配置核心获取相干的参数,同时会对这些参数进行监听,前面在配置核心外面扭转参数的值会实时推送给相干零碎;这样零碎就能够在不重启的状况下就更新了配置;
利用现有的一些中间件咱们就能很快实现一个配置核心,比方 Zookeeper 提供了对某个 Node 进行监听的性能,MQ 和 Redis 都有公布订阅的性能,所以用来实时推送变更再好不过了;

  • Zookeeper 形式

能够间接应用 PathChildrenCache 用来监听子节点的 CHILD_ADDED,CHILD_UPDATED,CHILD_REMOVED 事件;这样如果在 Zookeeper 服务端对节点的值就行更新,客户端会触发以上三个事件:

private void watcherPath(String path) {PathChildrenCache cache = new PathChildrenCache(client, path, true);
    cache.start(StartMode.POST_INITIALIZED_EVENT);
    cache.getListenable().addListener(new PathChildrenCacheListener() {
        @Override
        public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {switch (event.getType()) {
            case CHILD_ADDED:
                break;
            case CHILD_UPDATED:
                String logLevel = new String(event.getData().getData());
                 // 日志级别更新解决
                break;
            case CHILD_REMOVED:
                break;
            default:
                break;
            }
        }
    });
}
  • MQ 形式

MQ 个别都有 Queue 和 Topic 形式,Topic 形式其实就是订阅公布模式,所有的集群节点能够订阅某个 Topic,这样公布端发送更新日志级别的音讯,其余订阅节点都能收到:

// 日志等级 Topic
private final String TOPIC = "LOGLEVEL";
 
private void watcherPaths() throws JMSException {Topic topic = session.createTopic(TOPIC);
    MessageConsumer consumer = session.createConsumer(topic);
    consumer.setMessageListener(new MessageListener() {
        @Override
        public void onMessage(Message message) {TextMessage tm = (TextMessage) message;
            String logLevel = tm.getText();
            // 日志级别更新解决
        }
    });
}
  • Redis 形式

Redis 其实除了缓存的性能,也提供了相似 MQ 的公布订阅的模式;集群节点通过订阅一个 channel,公布端通过此 channel 来公布音讯:

private void watcherPaths() throws JMSException {jedis.subscribe(new JedisPubSub() {
        @Override
        public void onMessage(String channel, String message) {
             String logLevel = message;
             // 日志级别更新解决
        }
    },"LOGLEVEL");
}

SpringBoot 内置

SpringBoot2.0 之后能够通过 actuator 动静调整日志级别,次要是通过裸露 loggers 这个 endpoint 来实现,具体步骤如下:

  • 须要引入 actuator
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 裸露 loggers

在 application.properties 中增加如下配置:

management.endpoints.web.exposure.include=loggers
  • 查看日志级别

启动服务能够通过:

http://[ip]:[port]/actuator/loggers

查看以后我的项目每个包的日志级别:

{
levels: ["OFF","ERROR","WARN","INFO","DEBUG","TRACE"],
loggers: {
   ROOT: {
      configuredLevel: "INFO",
      effectiveLevel: "INFO"
   },
...
}
  • 动静批改日志级别

发送 POST 申请到:

http://[ip]:[port]/actuator/loggers/[包门路]

须要在 body 中指定 configuredLevel 参数;
比方批改整个我的项目日志级别为 error:

http://[ip]:[port]/actuator/loggers/root

对于 SpringBoot 外部是如何实现动静扭转日志级别的,能够查看其实现外围类 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);
    }
    ...
}

具体通过 LoggingSystem 来对日志零碎动静扭转级别,下面也介绍了支流应用的日志零碎,SpringBoot 也都反对这些零碎,这是一个抽象类,具体实现类:

  • JavaLoggingSystem
  • Log4J2LoggingSystem
  • LogbackLoggingSystem
  • NoOpLoggingSystem

别离对应了几种日志零碎,这几个类外部其实也是调用下面介绍的办法去扭转日志级别,当然 SpringBoot 主动会辨认出以后应用的是哪个日志零碎,而后应用哪个 LoggingSystem;

总结

大部分公司其实更多的还是应用配置核心的形式来动静扭转日志级别,这种形式更加灵便,而且配置核心曾经成为很多公司的标配组件,不光用来扭转日志级别,所有有可能扭转的参数都能够应用。

感激关注

能够关注微信公众号「 回滚吧代码 」,第一工夫浏览,文章继续更新;专一 Java 源码、架构、算法和面试。

退出移动版