<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          求求你,別再隨便打日志了,教你動態(tài)修改日志級別!

          共 11844字,需瀏覽 24分鐘

           ·

          2020-12-30 17:38

          ???
          △Hollis, 一個對Coding有著獨特追求的人△
          這是Hollis的第?324?篇原創(chuàng)分享
          作者 l Hollis
          來源 l Hollis(ID:hollischuang)
          之前寫過一篇文章《服務被干爆了!竟然是日志的鍋》,介紹過一次大促故障,是因為日志量激增,導致服務器差點掛掉。
          在那次問題發(fā)生后,我開發(fā)了一個簡單的日志降級的小工具,通過配置的方式,動態(tài)推送日志級別,動態(tài)修改線上的日志輸出級別。并且把這份配置的修改配置到我們的預案平臺上,大促期間進行定時或者緊急預案處理。
          么,這篇文章就來簡單介紹下思路以及代碼實現(xiàn)。


          日志級別
          在開始正文前簡單介紹下日志級別,不同的日志框架支持不同的日志級別,其中比較常見的就是Log4j和Logback。
          在Log4j中支持8種日志級別,優(yōu)先級從高到低依次為:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。
          Logback中支持7種日志級別,優(yōu)先級從高到低分別是:OFF、ERROR、WARN、INFO、DEBUG、TRACE、ALL。
          可以看到常見的ERROR、WARN、INFO、DEBUG,這兩者都是支持的。
          所謂設置日志的輸出級別表示的是輸出的日志的最低級別,也就是說,如果我們把級別設置成INFO,那么包括INFO在內以及比INFO優(yōu)先級高的級別的日志都可以輸出。
          無論是Log4j還是Logback,都是通過日志的配置文件來控制日志輸出級別的。這里就不詳述了。


          日志框架
          上面我們提到了Log4j和Logback,這兩種都是比較常用的日志框架。
          但是很多時候,我們在代碼中打印日志并不是直接使用這種日志框架來進行的,而是依賴了一個日志門面來進行的,如slf4j、commons-logging等。
          一般最最常用的方法就是通過slf4j提供的LoggerFactory的getLogger來獲取Logger,然后進行日志打印
          private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(LoggerService.class);

          public?void?test(){
          ????LOGGER.info("hollis?log?test");
          }
          當我們使用LoggerFactory.getLogger方法創(chuàng)建一個Logger對象的時候,會給他傳入一個loggerName,通過這個loggerName來唯一識別一個Logger,如上面的方式就是使用LoggerService這個類的全路徑名作為其loggerName。
          loggerName是每一個Logger的配置信息一部分,除此之外還有日志輸出級別等信息。
          關于為什么不直接使用log4j和logback打印日志,我在《為什么阿里巴巴禁止工程師直接使用日志系統(tǒng)(Log4j、Logback)中的 API》中分析過。


          Arthas改變日志級別
          在開始介紹代碼實現(xiàn)之前,先介紹一個工具,也可以幫助我們的動態(tài)修改日志級別。
          那就是阿里開源的神器——Arthas (https://arthas.aliyun.com/doc/ )。
          Arthas提供了一個logger命令,這個命令可以查看和更新logger信息,包括日志級別。
          查看指定名字的logger信息
          [arthas@2062]$?logger?-n?org.springframework.web
          ?name???????????????????????????????????org.springframework.web
          ?class??????????????????????????????????ch.qos.logback.classic.Logger
          ?classLoader????????????????????????????sun.misc.Launcher$AppClassLoader@2a139a55
          ?classLoaderHash????????????????????????2a139a55
          ?level??????????????????????????????????null
          ?effectiveLevel?????????????????????????INFO
          ?additivity?????????????????????????????true
          ?codeSource?????????????????????????????file:/Users/hengyunabc/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar
          更新logger level
          [arthas@2062]$?logger?--name?ROOT?--level?debug
          update?logger?level?success.
          簡單吧,使用一個命令就可以修改機日志級別了。
          但是Arthas目前對于集群的支持并不是特別的友好,雖然他支持了通過Arthas Tunnel Server/Client 來遠程管理/連接多個Agent,但是使用起來還不是很方便,并且對于命令的使用要求比較高。
          還有就是我們系統(tǒng)通過一個工具,方便我們在大促期間通過預案方式動態(tài)調整日志級別,這方面使用arthas就不是很方便了。


          代碼實現(xiàn)
          我寫的這個工具功能很簡單,就是提供動態(tài)修改日志級別的入口,方便用戶動態(tài)修改級別。
          并且為了方便使用,我將他封裝在一個Spring Boot Starter里面了,還有就是將他直接對接到公司內部的配置中心中,可以方便的通過配置中心一鍵修改日志級別。
          首先看下其中最核心的功能,那就是動態(tài)修改日志級別的部分,代碼如下

          import?org.slf4j.Logger;
          import?org.slf4j.LoggerFactory;
          import?org.springframework.beans.factory.annotation.Autowired;
          import?org.springframework.boot.logging.LogLevel;
          import?org.springframework.boot.logging.LoggerConfiguration;
          import?org.springframework.boot.logging.LoggingSystem;

          import?java.util.Collections;
          import?java.util.List;
          import?java.util.Optional;
          import?java.util.stream.Collectors;

          import?static?org.springframework.boot.logging.LoggingSystem.ROOT_LOGGER_NAME;

          /**
          ?*?日志級別設置服務類
          ?*
          ?*?@author?Hollis
          ?*/

          public?class?LoggerLevelSettingService?{

          ????@Autowired
          ????private?LoggingSystem?loggingSystem;

          ????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(LoggerLevelSettingService.class);

          ????public?void?setRootLoggerLevel(String?level)?{

          ????????LoggerConfiguration?loggerConfiguration?=?loggingSystem.getLoggerConfiguration(ROOT_LOGGER_NAME);

          ????????if?(loggerConfiguration?==?null)?{
          ????????????if?(LOGGER.isErrorEnabled())?{
          ????????????????LOGGER.error("no?loggerConfiguration?with?loggerName?"?+?level);
          ????????????}
          ????????????return;
          ????????}

          ????????if?(!supportLevels().contains(level))?{
          ????????????if?(LOGGER.isErrorEnabled())?{
          ????????????????LOGGER.error("current?Level?is?not?support?:?"?+?level);
          ????????????}
          ????????????return;
          ????????}

          ????????if?(!loggerConfiguration.getEffectiveLevel().equals(LogLevel.valueOf(level)))?{
          ????????????if?(LOGGER.isInfoEnabled())?{
          ????????????????LOGGER.info("setRootLoggerLevel?success,old?level?is??'"?+?loggerConfiguration.getEffectiveLevel()
          ????????????????????+?"'?,?new?level?is?'"?+?level?+?"'");
          ????????????}
          ????????????loggingSystem.setLogLevel(ROOT_LOGGER_NAME,?LogLevel.valueOf(level));
          ????????}
          ????}

          ????private?List?supportLevels()?{
          ????????return?loggingSystem.getSupportedLogLevels().stream().map(Enum::name).collect(Collectors.toList());
          ????}
          }

          以上代碼,就是根據(jù)用戶傳入的level的級別,將應用的ROOT日志輸出級別修改掉。
          這里面用到了一個關鍵的服務:
          org.springframework.boot.logging.LoggingSystem
          LoggingSystem服務是SpringBoot對日志系統(tǒng)的抽象,是一個頂層的抽象類。他有很多具體的實現(xiàn)
          ?
          通過上圖,我們可以發(fā)現(xiàn)目前SpringBoot目前支持4種類型的日志,分別是JDK內置的Log(JavaLoggingSystem)以及Log4j(Log4JLoggingSystem)、Log4j2(Log4J2LoggingSystem)以及Logback(LogbackLoggingSystem)。
          LoggingSystem是個抽象類,內部有這幾個方法:
          • beforeInitialize方法:日志系統(tǒng)初始化之前需要處理的事情。抽象方法,不同的日志架構進行不同的處理

          • initialize方法:初始化日志系統(tǒng)。默認不進行任何處理,需子類進行初始化工作

          • cleanUp方法:日志系統(tǒng)的清除工作。默認不進行任何處理,需子類進行清除工作

          • getShutdownHandler方法:返回一個Runnable用于當jvm退出的時候處理日志系統(tǒng)關閉后需要進行的操作,默認返回null,也就是什么都不做

          • setLogLevel方法:抽象方法,用于設置對應logger的級別

          SpringBoot在啟動時,會完成LoggingSystem的初始化,這部分代碼是在LoggingApplicationListener中實現(xiàn)的
          /**
          ?*?執(zhí)行LoggingSystem初始化的前置操作
          ?*/

          private?void?onApplicationStartingEvent(ApplicationStartingEvent?event)?{
          ????//獲取LoggingSystem的真實實現(xiàn),
          ????//?此處會根據(jù)不同的日志框架獲取不同的實現(xiàn),
          ????// logback :LogbackLoggingSystem
          ????// log4j2:Log4J2LoggingSystem
          ????// javalog:JavaLoggingSystem
          ????this.loggingSystem?=?LoggingSystem
          ????????.get(event.getSpringApplication().getClassLoader());
          ????//執(zhí)行beforeInitialize方法完成初始化前置操作
          ????this.loggingSystem.beforeInitialize();
          }
          有了LoggingSystem以后,我們就可以通過他來動態(tài)的修改日志級別。他幫我們屏蔽掉了底層的具體日志框架。
          除了支持修改ROOT級別的日志以外,還可以支持用戶自定義的日志的級別修改,代碼實現(xiàn)如下:
          先定義一個LoggerConfig,用來封裝日志的配置
          /**
          ?*?the?config?of?logger
          ?*
          ?*?@author?Hollis
          ?*/

          public?class?LoggerConfig?{

          ????/**
          ?????*?the?name?of?the?logger
          ?????*/

          ????private?String?loggerName;

          ????/**
          ?????*?the?log?level
          ?????*
          ?????*?@see?LogLevel
          ?????*/

          ????private?String?level;

          ????public?String?getLoggerName()?{
          ????????return?loggerName;
          ????}

          ????public?void?setLoggerName(String?loggerName)?{
          ????????this.loggerName?=?loggerName;
          ????}

          ????public?String?getLevel()?{
          ????????return?level;
          ????}

          ????public?void?setLevel(String?level)?{
          ????????this.level?=?level;
          ????}
          }

          接著提供方法動態(tài)修改日志級別:

          public?void?setLoggerLevel(List?configList)?{

          ????Optional.ofNullable(configList).orElse(Collections.emptyList()).forEach(
          ????????config?->?{
          ????????????LoggerConfiguration?loggerConfiguration?=?loggingSystem.getLoggerConfiguration(config.getLoggerName());

          ????????????if?(loggerConfiguration?==?null)?{
          ????????????????if?(LOGGER.isErrorEnabled())?{
          ????????????????????LOGGER.error("no?loggerConfiguration?with?loggerName?"?+?config.getLoggerName());
          ????????????????}
          ????????????????return;
          ????????????}

          ????????????if?(!supportLevels().contains(config.getLevel()))?{
          ????????????????if?(LOGGER.isErrorEnabled())?{
          ????????????????????LOGGER.error("current?Level?is?not?support?:?"?+?config.getLevel());
          ????????????????}
          ????????????????return;
          ????????????}

          ????????????if?(LOGGER.isInfoEnabled())?{
          ????????????????LOGGER.info("setLoggerLevel?success?for?logger?'"?+?config.getLoggerName()?+?"'?,old?level?is??'"
          ????????????????????+?loggerConfiguration.getEffectiveLevel()
          ????????????????????+?"'?,?new?level?is?'"?+?config.getLevel()?+?"'");
          ????????????}
          ????????????loggingSystem.setLogLevel(config.getLoggerName(),?LogLevel.valueOf(config.getLevel()));
          ????????}
          ????);
          }
          以上,根據(jù)用戶傳入的LoggerConfig,修改指定的loggerName對應的loggerLevel。至于LoggerLevel是怎么來的,就可以通過配置的方式傳入,比如解析JSON格式的配置或者YML文件等。
          如我們可以在配置中心中采用以下配置來控制日志級別,并推送:
          [{'loggerName':'com.hollis.degradation.core.logger.LoggerLevelSettingService','level':'WARN'}]
          以上配置,會使得loggerName為com.hollis.degradation.core.logger.LoggerLevelSettingService的日志的級別動態(tài)修改為WARN,另外,如果配置信息如下:
          [{'loggerName':'com.hollis.degradation.core.logger','level':'WARN'}]
          那么,就會將以com.hollis.degradation.core.logger這個包路徑下面的所有的類為LoggerName的日志輸出的級別全都動態(tài)修改為WARN。
          當然,這個配置也支持配置多個Logger的級別,如果是以下配置內容:
          [
          ??{'loggerName':'com.hollis.degradation.core.logger','level':'WARN'}
          ??,{'loggerName':'com.hollis.degradation.core.logger.LoggerLevelSettingService','level':'INFO'}
          ]
          加入代碼中有多個日志,他們的定義方法分別為
          private?static?final?Logger?LOGGER1?=?LoggerFactory.getLogger(LoggerLevelSettingService.class);
          private?static?final?Logger?LOGGER2?=?LoggerFactory.getLogger(TestService.class);
          private?static?final?Logger?LOGGER3?=?LoggerFactory.getLogger(DebugService.class);
          那么,配置生效后,會使得以上的LOGGER1的輸出級別為INFO,而LOGGER2和LOGGER3的級別為WARN。
          除此以外,上面的日志級別修改,可能會影響到我們自己這個工具本身的日志輸出,所以,我們提供了一個方法,可以直接修改我們自己這個日志服務的日志級別
          public?void?setDegradationLoggerLevel(String?level)?{

          ????LoggerConfiguration?loggerConfiguration?=?loggingSystem.getLoggerConfiguration(
          ????????this.getClass().getName());

          ????if?(loggerConfiguration?==?null)?{
          ????????if?(LOGGER.isWarnEnabled())?{
          ????????????LOGGER.warn("no?loggerConfiguration?with?loggerName?"?+?level);
          ????????}
          ????????return;
          ????}

          ????if?(!supportLevels().contains(level))?{
          ????????if?(LOGGER.isErrorEnabled())?{
          ????????????LOGGER.error("current?Level?is?not?support?:?"?+?level);
          ????????}
          ????????return;
          ????}

          ????if?(!loggerConfiguration.getEffectiveLevel().equals(LogLevel.valueOf(level)))?{
          ????????loggingSystem.setLogLevel(this.getClass().getName(),?LogLevel.valueOf(level));
          ????}
          }
          有了以上的LoggerLevelSettingService類以后,基本具備了動態(tài)修改日志的能力,接下來就是想辦法通過配置中心動態(tài)修改日志級別了。
          這里面因為不同的配置中心用法不同,我只是拿我們自己的配置中心簡單舉例
          /**
          ?*?降級開關注冊器
          ?*
          ?*?@author?Hollis
          ?*/

          public?class?DegradationSwitchInitializer?implements?Listener,?InitializingBean?{

          ????//從配置項中讀取應用名,方便注冊到配置中心
          ????@Value("${project.name}")
          ????private?String?appName;

          ????@Autowired
          ????private?LoggerLevelSettingService?loggerLevelSettingService;

          ????//配置中心值發(fā)生變化會自動回調該方法
          ????@Override
          ????public?void?valueChange(String?appName,?String?nameSpace,?String?name,
          ????????????????????????????String?value)?{

          ????????if?(name.equals(rootLogLevel.name()))?{
          ????????????loggerLevelSettingService.setRootLoggerLevel(value);
          ????????}

          ????????if?(name.equals(logLevelConfig.name()))?{
          ????????????List?loggerConfigs?=?JSON.parseArray(value,?LoggerConfig.class);
          ????????????loggerLevelSettingService.setLoggerLevel(loggerConfigs);
          ????????}

          ????????//將降級工具的日志輸出級別設置成INFO,保證其日志可以正常輸出
          ????????loggerLevelSettingService.setDegradationLoggerLevel("INFO");
          ????}

          ????@Override
          ????public?void?afterPropertiesSet()?{
          ????????//將服務配置到配置中心
          ????????ConfigCenterManager.addListener(this);
          ????????ConfigCenterManager.init(appName,?DegradationConfig.class);
          ????}
          }
          以上,我們實現(xiàn)了監(jiān)聽配置中心的值的變化,動態(tài)修改日志級別。
          基本功能就都完成了,接下來可以考慮如何讓其他應用快速接入,那就是定義一個Starter,可以方便快速接入。主要代碼如下:
          先定義一個Configuration類:
          /**
          ?*?@author?Hollis
          ?*/

          @Configuration
          @ConditionalOnProperty(prefix?=?"hollis.degradation",?name?=?"enable",?havingValue?=?"true")
          public?class?HollisDegradationAutoConfiguration?{

          ????@Bean
          ????@ConditionalOnMissingBean
          ????@ConditionalOnProperty(name?=?"project.name")
          ????public?LoggerLevelSettingService?loggerLevelSettingService()?{
          ????????return?new?LoggerLevelSettingService();
          ????}

          ????@Bean
          ????@ConditionalOnMissingBean
          ????@ConditionalOnBean(value?=?LoggerLevelSettingService.class)
          ????public?DegradationSwitchInitializer?degradationSwitchInitializer()?{
          ????????return?new?DegradationSwitchInitializer();
          ????}
          }
          在這個類里面定義兩個bean,并且bean定義的前提是應用中配置了以下兩個配置項:
          hollis.degradation.enable?=?true
          project.name?=?test
          接下來就是定一個spring.factories文件,定義內容如下:
          org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hollis.degradation.starter.autoconfiguration.HollisDegradationAutoConfiguration
          以上,只需要在需要引入降級工具的應用中,引入我們的這個starter,并且配置兩個配置項即可。
          接入后,可以方便的在配置中心中動態(tài)修改單機或者集群的日志輸出級別,并且可以在大促期間配置到預案平臺上,通過緊急預案快速執(zhí)行。


          代碼實現(xiàn)
          以上,基本實現(xiàn)了很多基本的功能,實現(xiàn)時考慮的因素主要有以下幾個:
          • 1、通用性。要同時可以支持不同的日志框架,客戶端使用的日志框架不影響我們的功能,并且客戶端不需要關心自己的日志框架的區(qū)別。

          • 2、可配置性??梢詫⑴渲眯畔⑼ㄟ^外部配置中心推送,可以快速進行調整。

          • 3、易用性。通過封裝到SpringBoot Starter中,方便客戶端快速接入。

          • 4、無侵入性。框架的使用不應該影響到應用的正常運行。

          當然,這個工具只是我花了幾個小時擼出來的,其中還有很多不足,其實還有很多事情可以優(yōu)化,比如配置的格式可以支持多種、支持通過EndPoint查看日志配置情況等,這些都還沒有實現(xiàn)。
          本文只是提供一個思路,希望大家都能學會用工具化的方式解決日常工作中遇到的問題,學會造輪子。

          往期推薦

          HTTP/2做錯了什么?剛剛輝煌2年就要被棄用了!?


          央視曝光:全國第九大電商平臺倒了!創(chuàng)始人卷走260億,1200萬人被騙


          那個 CEO 寫下 70 萬行代碼的公司,馬上要上市了



          ?

          直面Java第343期:為什么TOMCAT要破壞雙親委派

          深入并發(fā)第013期:拓展synchronized——鎖優(yōu)化


          如果你喜歡本文,
          請長按二維碼,關注?Hollis.
          轉發(fā)至朋友圈,是對我最大的支持。

          點個?在看?
          喜歡是一種感覺
          在看是一種支持
          ↘↘↘
          ???
          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  我要看国产靠逼的 | 日韩免费黄 | 久久精品国产亚洲AV苍井空 | AV高清无码 | 天堂网站 |