Log4jConfigListener動態改變記錄級別及實現

摘要: 線上的系統出現了bug,可能是請求的數據出現了問題,這個時候,日誌就爲我們提供瞭解決問題的辦法。但是線上的產品系統,一般的優先級都在INFO之上,如果修日日誌級別,獲取豐富的信息,可能需要重啓服務,對線上的影響比較大。如何能做到 動態的修改日誌的級別,而且不用重啓服務,對線上環境的影響減少到最小呢?Log4jConfigListener就上場了



之前就聽說有這麼個功能,一直沒有用上,這次線上產品出現了bug了,就趁這個機會使用下。

Log4jConfigListener在spring-web中,需要添加maven的依賴,在pom中添加

<dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-web</artifactId>
               <version>${spring.version}</version>
</dependency>


在web.xml中配置

<context-param>
 <param-name>log4jConfigLocation</param-name>
 <param-value>classpath:log4j.xml</param-value>
</context-param>

<context-param>
 <param-name>log4jRefreshInterval</param-name>
 <param-value>60000</param-value>
</context-param>

<listener>
 <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>


這樣幾配置好了,可以部署到服務器上去了。平時根據項目的需求配置日誌的輸出級別,如果想動態修改日誌級別,只需要修改log4j.xml就可以了。

那麼,Log4jConfigListener做了什麼,可以知道文件變化了並加以應用,難道是起了個線程來做的?

讓我們看看源碼吧,首先看下Log4jConfigListener

public class Log4jConfigListener implements ServletContextListener {

 public void contextInitialized(ServletContextEvent event) {
   Log4jWebConfigurer.initLogging(event.getServletContext());
 }

 public void contextDestroyed(ServletContextEvent event) {
   Log4jWebConfigurer.shutdownLogging(event.getServletContext());
 }

}


這裏Log4jConfigListener使用了Log4jWebConfigure,讓我們繼續


public static void initLogging(ServletContext servletContext) {
   // Expose the web app root system property.
   if (exposeWebAppRoot(servletContext)) {
     WebUtils.setWebAppRootSystemProperty(servletContext);
   }

   // Only perform custom log4j initialization in case of a config file.
   String location = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
   if (location != null) {
     // Perform actual log4j initialization; else rely on log4j's default initialization.
     try {
       // Resolve system property placeholders before potentially
       // resolving a real path.
       location = SystemPropertyUtils.resolvePlaceholders(location);

       // Leave a URL (e.g. "classpath:" or "file:") as-is.
       if (!ResourceUtils.isUrl(location)) {
         // Consider a plain file path as relative to the web
         // application root directory.
         location = WebUtils.getRealPath(servletContext, location);
       }

       // Write log message to server log.
       servletContext.log("Initializing log4j from [" + location + "]");

       // Check whether refresh interval was specified.
       String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM);
       if (intervalString != null) {
         // Initialize with refresh interval, i.e. with log4j's watchdog thread,
         // checking the file in the background.
         try {
           long refreshInterval = Long.parseLong(intervalString);
           Log4jConfigurer.initLogging(location, refreshInterval);
         }
         catch (NumberFormatException ex) {
           throw new IllegalArgumentException("Invalid 'log4jRefreshInterval' parameter: " + ex.getMessage());
         }
       }
       else {
         // Initialize without refresh check, i.e. without log4j's watchdog thread.
         Log4jConfigurer.initLogging(location);
       }
     }
     catch (FileNotFoundException ex) {
       throw new IllegalArgumentException("Invalid 'log4jConfigLocation' parameter: " + ex.getMessage());
     }
   }
 }


這裏有幾行代碼需要是重點,


String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM);
Log4jConfigurer.initLogging(location, refreshInterval);


那Log4jConfigure.initLogging有幹了啥呢?


public static void initLogging(String location, long refreshInterval) throws FileNotFoundException {
   String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
   File file = ResourceUtils.getFile(resolvedLocation);
   if (!file.exists()) {
     throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
   }
   if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
     DOMConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
   }
   else {
     PropertyConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
   }
 }


獲取配置文件,根據log4配置文件的格式(xml,properties)方式進行加載xml,那麼一定是在DOMConfigurator.configureAndWatch 或者PropertyConfigurator.configureAndWatch裏面有個線程在做幕後工作,由於LZ

採用的是XML格式的配置文件,那就看下DOMConfigurator.configureAndWatch,看看它到底怎麼實現的吧。

public  static void configureAndWatch(String configFilename, long delay) {
   XMLWatchdog xdog = new XMLWatchdog(configFilename);
   xdog.setDelay(delay);
   xdog.start();
 }


XMLWatchdog,這是個WatchDog,哈哈,有啥動靜,自然躲不過watchDog的眼睛,還有start方法,看起來應該是Thread類,讓我們看看WatchDog的真面目吧。


class XMLWatchdog extends FileWatchdog {

   XMLWatchdog(String filename) {
   super(filename);
 }

 /**
    Call {@link DOMConfigurator#configure(String)} with the
    <code>filename</code> to reconfigure log4j. */

 public
 void doOnChange()
{
   new DOMConfigurator().doConfigure(filename,
             LogManager.getLoggerRepository());
 }
}


FileWatchDog


public abstract class FileWatchdog extends Thread{
.......

 abstract
 protected
 void doOnChange()
;
 
   
 public void run() {    
   while(!interrupted) {
     try {
     Thread.sleep(delay);
     } catch(InterruptedException e) {
 // no interruption expected
     }
     checkAndConfigure();
   }
 }
 protected void checkAndConfigure() {
........

   if(fileExists) {
     long l = file.lastModified(); // this can also throw a SecurityException
     if(l > lastModif) {           // however, if we reached this point this
 lastModif = l;              // is very unlikely.
 doOnChange();
 warnedAlready = false;
     }
   } else {
     if(!warnedAlready) {
 LogLog.debug("["+filename+"] does not exist.");
 warnedAlready = true;
     }
   }
 }
}


FileWatchDog有個抽象方法,doOnChange,就是對文件變化後的響應,抽象方法的定義,爲子類的擴展提供了可能。

我們看到,Log4jConfirgureListener也就是通過線程的方式掃描log4j.xml,當發現log4j的配置文件發生變化後就作出響應,從而做到了不重啓應用修改日誌的輸出級別。

通過閱讀源碼,我們更清楚的知道web.xml中的配置參數

log4jRefreshInterval的時間單位是MS



本文分享自微信公衆號 - 我的小碗湯(mysmallsoup)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章