当前位置: 首页 > 工具软件 > Just4Log > 使用案例 >

log4j之LogManager

吕向阳
2023-12-01

LogManager,获取Logger实例或者操作当前的LoggerRepository。
LogManager类的初始化过程在加载到内存前已经完成。
在使用log4j时, 我们一开始会获取Logger:

org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(this.getClass());

其中Logger.getLogger

   /**
   * Shorthand for <code>getLogger(clazz.getName())</code>.
   *
   * @param clazz The name of <code>clazz</code> will be used as the
   * name of the logger to retrieve.  See {@link #getLogger(String)}
   * for more detailed information.
   */
  static public Logger getLogger(Class clazz) {
    return LogManager.getLogger(clazz.getName());
  }

这里最主要工作在LogManager这个类里,
首先看static代码块:

    static {
        //这里使用默认的selector
        Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
        repositorySelector = new DefaultRepositorySelector(h);

        /** 查找log4j.properties   */
        //DEFAULT_INIT_OVERRIDE_KEY="log4j.defaultInitOverride"
        String override = OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
                null);

        // 若log4j.properties没被override,则获取默认配置
        if (override == null || "false".equalsIgnoreCase(override)) {
            //DEFAULT_CONFIGURATION_KEY="log4j.configuration"
            String configurationOptionStr = OptionConverter.getSystemProperty(
                    DEFAULT_CONFIGURATION_KEY,
                    null);
            //CONFIGURATOR_CLASS_KEY="log4j.configuratorClass"
            String configuratorClassName = OptionConverter.getSystemProperty(
                    CONFIGURATOR_CLASS_KEY,
                    null);

            URL url = null;

            //用户没设置log4j.configuration,则加载log4j.xml,或者log4j.properties
            if (configurationOptionStr == null) {
                //DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml"
                url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
                if (url == null) {
                    //DEFAULT_CONFIGURATION_FILE = "log4j.properties"
                    url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
                }
            } else {
                try {
                    url = new URL(configurationOptionStr);
                } catch (MalformedURLException ex) {
                    // so, resource is not a URL:
                    // attempt to get the resource from the class path
                    url = Loader.getResource(configurationOptionStr);
                }
            }

            // url不为空,剩下的工作交给OptionConverter.selectAndConfigure
            if (url != null) {
                LogLog.debug("Using URL [" + url + "] for automatic log4j configuration.");
                try {
                    OptionConverter.selectAndConfigure(url, configuratorClassName,
                            LogManager.getLoggerRepository());
                } catch (NoClassDefFoundError e) {
                    LogLog.warn("Error during default initialization", e);
                }
            } else {
                LogLog.debug("Could not find resource: [" + configurationOptionStr + "].");
            }
        } else {
            LogLog.debug("Default initialization of overridden by " +
                    DEFAULT_INIT_OVERRIDE_KEY + "property.");
        }
    }

LogManager中还有一些方法:

    //设置RepositorySelector
    static  public void setRepositorySelector(RepositorySelector selector, Object guard) 
                                                 throws IllegalArgumentException {
    if((LogManager.guard != null) && (LogManager.guard != guard)) {
      throw new IllegalArgumentException(
           "Attempted to reset the LoggerFactory without possessing the guard.");
    }

    if(selector == null) {
      throw new IllegalArgumentException("RepositorySelector must be non-null.");
    }

    LogManager.guard = guard;
    LogManager.repositorySelector = selector;
  }

  //
  static  public LoggerRepository getLoggerRepository() {
    if (repositorySelector == null) {
        repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());
        guard = null;
        Exception ex = new IllegalStateException("Class invariant violation");
        String msg =
                "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";
        if (isLikelySafeScenario(ex)) {
            LogLog.debug(msg, ex);
        } else {
            LogLog.error(msg, ex);
        }
    }
    return repositorySelector.getLoggerRepository();
  }

  public static  Logger getRootLogger() {
    return getLoggerRepository().getRootLogger();
  }

  public
  static 
  Logger getLogger(final String name) {
     // Delegate the actual manufacturing of the logger to the logger repository.
    return getLoggerRepository().getLogger(name);
  }

 /**
     Retrieve the appropriate {@link Logger} instance.  
  */
  public
  static 
  Logger getLogger(final Class clazz) {
     // Delegate the actual manufacturing of the logger to the logger repository.
    return getLoggerRepository().getLogger(clazz.getName());
  }


  /**
     Retrieve the appropriate {@link Logger} instance.  
  */
  public
  static 
  Logger getLogger(final String name, final LoggerFactory factory) {
     // Delegate the actual manufacturing of the logger to the logger repository.
    return getLoggerRepository().getLogger(name, factory);
  }  

  public
  static
  Logger exists(final String name) {
    return getLoggerRepository().exists(name);
  }

  public
  static
  Enumeration getCurrentLoggers() {
    return getLoggerRepository().getCurrentLoggers();
  }

  public
  static
  void shutdown() {
    getLoggerRepository().shutdown();
  }

  public
  static
  void resetConfiguration() {
    getLoggerRepository().resetConfiguration();
  }

现在看OptionConverter.selectAndConfigure是如何加载配置信息的:

OptionConverter.selectAndConfigure(url, //配置文件的url
                       configuratorClassName,//配置的类名,一般是空的
                       LogManager.getLoggerRepository());//从repositorySelector获取LoggerRepository

上代码:

static public void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) {
   Configurator configurator = null;
   String filename = url.getFile();

   if(clazz == null && filename != null && filename.endsWith(".xml")) {
     clazz = "org.apache.log4j.xml.DOMConfigurator";
   }

   if(clazz != null) {
     LogLog.debug("Preferred configurator class: " + clazz);
     configurator = (Configurator) instantiateByClassName(clazz,
                              Configurator.class,
                              null);
     if(configurator == null) {
      LogLog.error("Could not instantiate configurator ["+clazz+"].");
      return;
     }
   } else {
   //如果没有一些自定义的配置,正常是代码是走这里
     configurator = new PropertyConfigurator();
   }
    //然后读取配置文件
   configurator.doConfigure(url, hierarchy);
  }

再看PropertyConfigurator.doConfigure方法:
获取配置信息的代码省略

    Properties props = new Properties();
    //获取配置信息的代码省略
    //
    doConfigure(props, hierarchy);

对配置信息读取后进行的处理:

public void doConfigure(Properties properties, LoggerRepository hierarchy) {
    repository = hierarchy;
    String value = properties.getProperty(LogLog.DEBUG_KEY);
    if(value == null) {
      value = properties.getProperty("log4j.configDebug");
      if(value != null)
    LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
    }

    if(value != null) {
      LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
    }

      //
      //   if log4j.reset=true then
      //        reset hierarchy
    String reset = properties.getProperty(RESET_KEY);
    if (reset != null && OptionConverter.toBoolean(reset, false)) {
          hierarchy.resetConfiguration();
    }

    String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,
                               properties);
    if(thresholdStr != null) {
      hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,
                             (Level) Level.ALL));
      LogLog.debug("Hierarchy threshold set to ["+hierarchy.getThreshold()+"].");
    }

    //这里的三个方法,完成日志的配置
    configureRootCategory(properties, hierarchy);
    //配置属性log4j.loggerFactory,包括log4j.factory前缀的属性
    configureLoggerFactory(properties);
    //Parse non-root elements, such non-root categories and renderers.
    //解析renderer或者categories,非root元素
    parseCatsAndRenderers(properties, hierarchy);

    LogLog.debug("Finished configuring.");
    // We don't want to hold references to appenders preventing their
    // garbage collection.
    registry.clear();
  }

先来看第一个方法都做了些什么工作:

void configureRootCategory(Properties props, LoggerRepository hierarchy) {
    String effectiveFrefix = ROOT_LOGGER_PREFIX;
    String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);

    if(value == null) {
      value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
      effectiveFrefix = ROOT_CATEGORY_PREFIX;
    }

    if(value == null)
      LogLog.debug("Could not find root logger information. Is this OK?");
    else {
      Logger root = hierarchy.getRootLogger();
      synchronized(root) {
    parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
      }
    }
  }

从上面的代码可看出,log4j的配置文件,必须有属性log4j.rootLogger或者log4j.rootCategory。
当该属性的值不为空,调用parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value)处理。
该方法做了如下工作:
1. log4j.rootLogger或者log4j.rootCategory的值可以用”,”分隔
2. 获取每个值,进行日志级别的设置。
3. 清除当前日志所有appenders
4. 解析Appender并返回,添加到logger中

    void parseCategory(Properties props, Logger logger, String optionKey,
             String loggerName, String value) {

    LogLog.debug("Parsing for [" +loggerName +"] with value=[" + value+"].");
    // We must skip over ',' but not white space
    StringTokenizer st = new StringTokenizer(value, ",");

    // If value is not in the form ", appender.." or "", then we should set
    // the level of the loggeregory.

    if(!(value.startsWith(",") || value.equals(""))) {

      // just to be on the safe side...
      if(!st.hasMoreTokens())
    return;

      String levelStr = st.nextToken();
      LogLog.debug("Level token is [" + levelStr + "].");

      // If the level value is inherited, set category level value to
      // null. We also check that the user has not specified inherited for the
      // root category.
      if(INHERITED.equalsIgnoreCase(levelStr) || 
                                      NULL.equalsIgnoreCase(levelStr)) {
    if(loggerName.equals(INTERNAL_ROOT_NAME)) {
      LogLog.warn("The root logger cannot be set to null.");
    } else {
      logger.setLevel(null);
    }
      } else {
    logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
      }
      LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
    }

    // Begin by removing all existing appenders.
    logger.removeAllAppenders();

    Appender appender;
    String appenderName;
    while(st.hasMoreTokens()) {
      appenderName = st.nextToken().trim();
      if(appenderName == null || appenderName.equals(","))
    continue;
      LogLog.debug("Parsing appender named \"" + appenderName +"\".");
      appender = parseAppender(props, appenderName);
      if(appender != null) {
    logger.addAppender(appender);
      }
    }
  }

解析Appender并返回,代码如下:

Appender parseAppender(Properties props, String appenderName) {
    //从Hashtable中获取
    Appender appender = registryGet(appenderName);
    if((appender != null)) {
      LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
      return appender;
    }
    // Appender was not previously initialized.
    String prefix = APPENDER_PREFIX + appenderName;
    String layoutPrefix = prefix + ".layout";

    appender = (Appender) OptionConverter.instantiateByKey(props, prefix,
                          org.apache.log4j.Appender.class,
                          null);
    if(appender == null) {
      LogLog.error(
              "Could not instantiate appender named \"" + appenderName+"\".");
      return null;
    }
    appender.setName(appenderName);

    if(appender instanceof OptionHandler) {
      if(appender.requiresLayout()) {
    Layout layout = (Layout) OptionConverter.instantiateByKey(props,
                                  layoutPrefix,
                                  Layout.class,
                                  null);
    if(layout != null) {
      appender.setLayout(layout);
      LogLog.debug("Parsing layout options for \"" + appenderName +"\".");
      //configureOptionHandler(layout, layoutPrefix + ".", props);
          PropertySetter.setProperties(layout, props, layoutPrefix + ".");
      LogLog.debug("End of parsing for \"" + appenderName +"\".");
    }
      }
      final String errorHandlerPrefix = prefix + ".errorhandler";
      String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
      if (errorHandlerClass != null) {
            ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props,
                      errorHandlerPrefix,
                      ErrorHandler.class,
                      null);

            //如果对错误有设置处理事件,遍历
            if (eh != null) {
                  appender.setErrorHandler(eh);
                  LogLog.debug("Parsing errorhandler options for \"" + appenderName +"\".");
                  parseErrorHandler(eh, errorHandlerPrefix, props, repository);
                  final Properties edited = new Properties();
                  final String[] keys = new String[] { 
                          errorHandlerPrefix + "." + ROOT_REF,
                          errorHandlerPrefix + "." + LOGGER_REF,
                          errorHandlerPrefix + "." + APPENDER_REF_TAG
                  };
                  for(Iterator iter = props.entrySet().iterator();iter.hasNext();) {
                      Map.Entry entry = (Map.Entry) iter.next();
                      int i = 0;
                      for(; i < keys.length; i++) {
                          if(keys[i].equals(entry.getKey())) break;
                      }
                      if (i == keys.length) {
                          edited.put(entry.getKey(), entry.getValue());
                      }
                  }
                  PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
                  LogLog.debug("End of errorhandler parsing for \"" + appenderName +"\".");
            }

      }

      PropertySetter.setProperties(appender, props, prefix + ".");
      LogLog.debug("Parsed \"" + appenderName +"\" options.");
    }
    //这里过滤Appender信息,或有设置,按照设置过滤处理日志信息,然后输出到日志文件或控制台中。
    parseAppenderFilters(props, appenderName, appender);
    //这里将生成的appender放入Hashtable,以便于下次获取,不用重新生成
    registryPut(appender);
    return appender;
  }
 类似资料: