首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Log4jConfigListener动态改变记录级别及实现

Log4jConfigListener动态改变记录级别及实现

作者头像
我的小碗汤
发布2018-08-22 11:14:24
5390
发布2018-08-22 11:14:24
举报
文章被收录于专栏:我的小碗汤我的小碗汤

摘要: 线上的系统出现了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
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 进击云原生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档