1. 程式人生 > 其它 >如何動態改變日誌級別

如何動態改變日誌級別

技術標籤:後端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原始碼、架構、演算法和麵試