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

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

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

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

            <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就能夠了。app

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

讓咱們看看源碼吧,首先看下Log4jConfigListeneride

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,讓咱們繼續this

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());
			}
		}
	}

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

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,看看它到底怎麼實現的吧。


  static
  public
  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

若是你有空,不妨閱讀下源碼,這樣更有收穫。

因爲本人水平有限,若是不對的地方或須要補充的地方,請您指出。若是您是大牛,能夠忽略本文。

謝謝

相關文章
相關標籤/搜索