前言

对于日志级别,大部分我的项目可能都设置为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,这样公布端发送更新日志级别的音讯,其余订阅节点都能收到:

//日志等级Topicprivate 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源码、架构、算法和面试。