Logback+Osgi配置自动更新问题

软件版本
Logback-core-0.9.30
Logback-classic-0.9.30
Osgi equinox
Eclipse 3.7.2
存在问题
logback.xml配置文件,设置自动检测配置更新,<configuration scan="true"  debug="true">,但是启动后,修改配置文件没有自动更新。比如:
<logger name="com.company.server.main" >
		<level value="DEBUG" />
</logger>

level修改为INFO,debug级别日志继续输出
问题分析
经阅读源码,跟踪调试,反复验证,确认是logback与osgi配合使用时,存在的缺陷,将配置文件放在类路径下,比如放在src下,logback会自动在classpath中找到该文件,但是自动检测更新不起作用。(当然是有解决办法的,请看下文)
当把<configuration scan="true"  debug="true">,debug设置为true时,会输出logback内部的一些调试信息。项目启动时会输出:
14:06:35,846 |-INFO in ReconfigureOnChangeFilter{invocationCounter=0} - Will scan for changes in [[D:\workspace\fileaccess\src\test\resources\logback.xml]] every 60 seconds. 
14:06:35,846 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - Adding ReconfigureOnChangeFilter as a turbo filter

很明显,这段日志指的是,每60s会检测指定路径下的配置文件是否有变化,该路径就是你配置文件的绝对路径。
而osgi启动时,会怎么样呢?
14:32:58,509 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [bundleresource://62.fwk918077175/logback.xml]
14:32:58,510 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs multiple times on the classpath.
14:32:58,510 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [bundleresource://62.fwk918077175/logback.xml]
14:32:58,510 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [bundleresource://62.fwk918077175:38/logback.xml]
14:32:58,530 |-INFO in ch.qos.logback.core.joran.spi.ConfigurationWatchList@239a029e - URL [bundleresource://62.fwk918077175/logback.xml] is not of type file
14:32:58,579 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
14:32:58,579 |-INFO in ReconfigureOnChangeFilter{invocationCounter=0} - Will scan for changes in [[]] every 60 seconds. 

请注意以上日志,自动检测的目标是空的,先说结果,就是不能再侦测到任何更新了。原因是osgi自动给配置文件一个URL[bundleresource://62.fwk918077175:38/logback.xml],这导致logback在将该URL放入WatchList时,解析为NULL。而正常的URL应该是这种格式:file://D:\workspace\fileaccess\src\test\resources\logback.xml。 更具体的分析,在本文最后,有兴趣的同学可以继续看看。
先来说说解决办法
问题的原因,是解析文件URL出错,那么如何避免这个问题,办法就是logback的显式指定配置文件路径的办法。
在java启动参数中,加上-Dlogback.configurationFile=D:\eclipse-64bit\*\logback.xml(你自己的配置文件路径),这样配置文件就能被logback成功的加到watchlist中去了。就可以自动检测配置更新了。

详细分析
分析涉及logback源码,可以去官网http://logback.qos.ch/下载。
只提几个启动过程中,配置初始化的关键类,按调用先后顺序。
classic包ContextInitializer类
public void autoConfig() throws JoranException {
    StatusListenerConfigHelper.installIfAsked(loggerContext);
    URL url = findURLOfDefaultConfigurationFile(true);
    if (url != null) {
      configureByResource(url);
    } else {
      BasicConfigurator.configure(loggerContext);
    }
  }
public void configureByResource(URL url) throws JoranException {
    if (url == null) {
      throw new IllegalArgumentException("URL argument cannot be null");
    }
    if (url.toString().endsWith("groovy")) {
      if (EnvUtil.isGroovyAvailable()) {
        // avoid directly referring to GafferConfigurator so as to avoid
        // loading  groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
        GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
      } else {
        StatusManager sm = loggerContext.getStatusManager();
        sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.",
                loggerContext));
      }
    }
    if (url.toString().endsWith("xml")) {
      JoranConfigurator configurator = new JoranConfigurator();
      configurator.setContext(loggerContext);
      configurator.doConfigure(url);
    }
  }

Core包GenericConfigurator类
f
inal public void doConfigure(URL url) throws JoranException {
    try {
      informContextOfURLUsedForConfiguration(getContext(), url);
      URLConnection urlConnection = url.openConnection();
      // per http://jira.qos.ch/browse/LBCORE-105
      // per http://jira.qos.ch/browse/LBCORE-127
      urlConnection.setUseCaches(false);

      InputStream in = urlConnection.getInputStream();
      doConfigure(in);
      in.close();
    } catch (IOException ioe) {
      String errMsg = "Could not open URL [" + url + "].";
      addError(errMsg, ioe);
      throw new JoranException(errMsg, ioe);
    }
  }
static public void informContextOfURLUsedForConfiguration(Context context, URL url) {
    ConfigurationWatchListUtil.setMainWatchURL(context, url);
  }

Core包ConfigurationWatchListUtil类
public static void setMainWatchURL(Context context, URL url) {
    ConfigurationWatchList cwl = getConfigurationWatchList(context);
    if (cwl == null) {
      cwl = new ConfigurationWatchList();
      cwl.setContext(context);
      context.putObject(CoreConstants.CONFIGURATION_WATCH_LIST, cwl);
    } else {
      cwl.clear();
    }
    setConfigurationWatchListResetFlag(context, true);
    cwl.setMainURL(url);
  }

Core包ConfigurationWatchList类
public void setMainURL(URL mainURL) {
    // main url can be null
    this.mainURL = mainURL;
    if (mainURL != null)
      addAsFileToWatch(mainURL);
  }
private void addAsFileToWatch(URL url) {
    File file = convertToFile(url);
    if (file != null) {
      fileWatchList.add(file);
      lastModifiedList.add(file.lastModified());
    }
  }
@SuppressWarnings("deprecation")
  File convertToFile(URL url) {
    String protocol = url.getProtocol();
    if ("file".equals(protocol)) {
      File file = new File(URLDecoder.decode(url.getFile()));
      return file;
    } else {
      addInfo("URL [" + url + "] is not of type file");
      return null;
    }
  }

解析就是convertToFile这里出的问题,URL[bundleresource://62.fwk918077175:38/logback.xml],会返回NULL。所以就没有加入到fileWatchList里面。

ReconfigureOnChangeFilter是检测配置更新的关键类,文章最初提到的日志里,输出每60s检测一次,就是它输出的,它取的watchList就是fileWatchList。
所以,问题已经很明显了。
更多的,请阅读源码。

你可能感兴趣的:(Logback+Osgi配置自动更新问题)