slf4j和log4j的源码及异步日志

来源:互联网 发布:scarsong知乎 编辑:程序博客网 时间:2024/06/11 00:12
我们在项目中,一般这样使用slf4j来记录日志:
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Test.class);
从这个日志Logger创建入手来看看源码:
org.slf4j.LoggerFactory.getLogger(String name){    ILoggerFactory iLoggerFactory = getILoggerFactory(); //1处    return iLoggerFactory.getLogger(name);}
或者
public static Logger getLogger(Class<?> clazz) {    Logger logger = getLogger(clazz.getName()); //2处    if (DETECT_LOGGER_NAME_MISMATCH) {        Class<?> autoComputedCallingClass = Util.getCallingClass();        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {            Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),                            autoComputedCallingClass.getName()));            Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");        }    }    return logger;}
2处也会调用getLogger(String name)方法。
看回1处的getILoggerFactory()方法,再调用getLogget方法,知道是使用工厂模式来创建Logger对象。
public static ILoggerFactory getILoggerFactory() {    if (INITIALIZATION_STATE == UNINITIALIZED) {        synchronized (LoggerFactory.class) {            if (INITIALIZATION_STATE == UNINITIALIZED) { //3处                INITIALIZATION_STATE = ONGOING_INITIALIZATION;                performInitialization();            }        }    }    switch (INITIALIZATION_STATE) {    case SUCCESSFUL_INITIALIZATION: //4处        return StaticLoggerBinder.getSingleton().getLoggerFactory();    case NOP_FALLBACK_INITIALIZATION:        return NOP_FALLBACK_FACTORY;    case FAILED_INITIALIZATION:        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);    case ONGOING_INITIALIZATION:        // support re-entrant behavior.        // See also http://jira.qos.ch/browse/SLF4J-97        return SUBST_FACTORY;    }    throw new IllegalStateException("Unreachable code");}
在3处,如果状态是未初化的,则先进行初始化,初始化成功后,在4处,使用单例模式得到StaticLoggerBinder对象,获取Logger的工厂对象。
下面源码StaticLoggerBinder构造方法中new了工厂对象Log4jLoggerFactory
private StaticLoggerBinder() {    loggerFactory = new Log4jLoggerFactory();    try {        @SuppressWarnings("unused")        Level level = Level.TRACE;    } catch (NoSuchFieldError nsfe) {        Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");    }}
所以知道在上面源码中的"1处"取到的ILoggerFactory工厂对象是org.slf4j.impl.Log4jLoggerFactory的实例,接着看看Log4jLoggerFactory.getLogger(String name)
public class Log4jLoggerFactory implements ILoggerFactory {    // key: name (String), value: a Log4jLoggerAdapter;    ConcurrentMap<String, Logger> loggerMap;    public Log4jLoggerFactory() {        loggerMap = new ConcurrentHashMap<String, Logger>();    }    public Logger getLogger(String name) {        Logger slf4jLogger = loggerMap.get(name);        if (slf4jLogger != null) {            return slf4jLogger;        } else {            org.apache.log4j.Logger log4jLogger;            if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) //ROOT_LOGGER_NAME = "ROOT"                log4jLogger = LogManager.getRootLogger();            else                log4jLogger = LogManager.getLogger(name); //5处            Logger newInstance = new Log4jLoggerAdapter(log4jLogger); //51处            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);            return oldInstance == null ? newInstance : oldInstance;        }    }}
Log4jLoggerFacotry的Map loggerMap保存着org.slf4j.Logger的key-value映射关系。
对源码的“5处”,其中LogManager先get到log4j的Logger log4jLogger对象
public class LogManager {  /**   * @deprecated This variable is for internal use only. It will   * become package protected in future versions.   * */  static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";   static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";       /**   * @deprecated This variable is for internal use only. It will   * become private in future versions.   * */  static final public String DEFAULT_CONFIGURATION_KEY="log4j.configuration";  /**   * @deprecated This variable is for internal use only. It will   * become private in future versions.   * */  static final public String CONFIGURATOR_CLASS_KEY="log4j.configuratorClass";  /**  * @deprecated This variable is for internal use only. It will  * become private in future versions.  */  public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";  static private RepositorySelector repositorySelector;  static {    // By default we use a DefaultRepositorySelector which always returns 'h'.    Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); //61处    repositorySelector = new DefaultRepositorySelector(h); //6处    /** Search for the properties file log4j.properties in the CLASSPATH.  */    String override = OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null);    // if there is no default init override, then get the resource    // specified by the user or the default config file.    if(override == null || "false".equalsIgnoreCase(override)) {      //使用 -Dlog4j.configuration=log4j.properties 来指定log4j的配置文件      String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null);      String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null);      URL url = null;      // if the user has not specified the log4j.configuration      // property, we search first for the file "log4j.xml" and then      // "log4j.properties"      if(configurationOptionStr == null) {        //如果没有指定log4j的配置文件,先加载log4j.xml            url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);        if(url == null) {          //没有log4j.xml,则加载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);        }          }            // If we have a non-null url, then delegate the rest of the      // configuration to the OptionConverter.selectAndConfigure      // method.      if(url != null) {        LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");        try {            //配置log4j日志            OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository()); //62处        } 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.");    }    }   static public LoggerRepository getLoggerRepository() {    if (repositorySelector == null) { //7处        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(); //8处  }  public static Logger getLogger(final String name) {     // Delegate the actual manufacturing of the logger to the logger repository.    return getLoggerRepository().getLogger(name);  }}
LogManager里,先看 -Dlog4j.configuration=log4j.properties 来指定log4j的配置文件,如果没有指定log4j的配置文件,先加载log4j.xml,没有log4j.xml,则加载log4j.properties。
看看LogManager的getLogger方法里面其实调用了getLoggerRepository(),取得LoggerRepository对象再getLogger(name)得到Logger对象的。
可以先看看getLoggerRepository()方法
7处的repositorySelector在LogManager类加载时的static语块已经new了一个DefaultRepositorySelector对象,即6处,并设置了LoggerRepository-Hierarchy对象,设置日志的DEBUG级别
去看看61处new一个Hierarchy对象:
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {  private LoggerFactory defaultFactory;  private Vector listeners;  Hashtable ht;  Logger root;  RendererMap rendererMap;  int thresholdInt;  Level threshold;  boolean emittedNoAppenderWarning = false;  boolean emittedNoResourceBundleWarning = false;  private ThrowableRenderer throwableRenderer = null;  public Hierarchy(Logger root) {    ht = new Hashtable();    listeners = new Vector(1);    this.root = root; //log4j.Logger    // Enable all level levels by default.    setThreshold(Level.ALL);    this.root.setHierarchy(this); //62处    rendererMap = new RendererMap();    defaultFactory = new DefaultCategoryFactory();  }
在62处的setHierarchy(LoggerRepository repository)方法是Category的方法,而log4j.Logger继承了Category:
// Categories need to know what Hierarchy they are inprotected LoggerRepository repository;final void setHierarchy(LoggerRepository repository) {    this.repository = repository;}所以8处getLoggerRepository()得到的是Hierarchy对象。public class DefaultRepositorySelector implements RepositorySelector {  final LoggerRepository repository;  public DefaultRepositorySelector(LoggerRepository repository) {    this.repository = repository;  }}
所以getLoggerRepository().getLogger(name)就是调用了Hierarchy的getLogger(String name)方法,看看下面的9处
public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {  private Vector listeners;  Hashtable ht;  Logger root;  public Hierarchy(Logger root) {    ht = new Hashtable();    listeners = new Vector(1);    this.root = root;    // Enable all level levels by default.    setThreshold(Level.ALL);    this.root.setHierarchy(this);    rendererMap = new RendererMap();    defaultFactory = new DefaultCategoryFactory();  }  public Logger getLogger(String name) { //9处    return getLogger(name, defaultFactory);  }  public Logger getLogger(String name, LoggerFactory factory) {    //System.out.println("getInstance("+name+") called.");    CategoryKey key = new CategoryKey(name);    // Synchronize to prevent write conflicts. Read conflicts (in    // getChainedLevel method) are possible only if variable    // assignments are non-atomic.    Logger logger;    synchronized(ht) {      Object o = ht.get(key);      if(o == null) {    logger = factory.makeNewLoggerInstance(name); //10处    logger.setHierarchy(this);    ht.put(key, logger);    updateParents(logger); //11处    return logger;      } else if(o instanceof Logger) {    return (Logger) o;      } else if (o instanceof ProvisionNode) {    //System.out.println("("+name+") ht.get(this) returned ProvisionNode");    logger = factory.makeNewLoggerInstance(name);    logger.setHierarchy(this);    ht.put(key, logger);    updateChildren((ProvisionNode) o, logger);    updateParents(logger);    return logger;      }      else {    // It should be impossible to arrive here    return null;  // but let's keep the compiler happy.      }    }  }
在10处创建org.apache.log4j.Logger对象,并保存在Hashtable ht里<name, org.apache.log4j.Logger>。
在11处updateParents方法:
private void updateParents(Logger cat) {    String name = cat.name;    int length = name.length();    boolean parentFound = false;    //System.out.println("UpdateParents called for " + name);    // if name = "w.x.y.z", loop thourgh "w.x.y", "w.x" and "w", but not "w.x.y.z"    for(int i = name.lastIndexOf('.', length-1); i >= 0;                                     i = name.lastIndexOf('.', i-1))  {      String substr = name.substring(0, i);      //System.out.println("Updating parent : " + substr);      CategoryKey key = new CategoryKey(substr); // simple constructor      Object o = ht.get(key);      // Create a provision node for a future parent.      if(o == null) {    //System.out.println("No parent "+substr+" found. Creating ProvisionNode.");    ProvisionNode pn = new ProvisionNode(cat);    ht.put(key, pn);      } else if(o instanceof Category) {    parentFound = true;    cat.parent = (Category) o;    //System.out.println("Linking " + cat.name + " -> " + ((Category) o).name);    break; // no need to update the ancestors of the closest ancestor      } else if(o instanceof ProvisionNode) {    ((ProvisionNode) o).addElement(cat);      } else {    Exception e = new IllegalStateException("unexpected object type " +                    o.getClass() + " in ht.");    e.printStackTrace();      }    }    // If we could not find any existing parents, then link with root.    if(!parentFound)      cat.parent = root;  }
看这里源码,比如为w.x.y.z.Foo类创建的Logger,则还会创建它的上一级w.x.y...
到处,就算是已经创建好log4j的Logger对象log4jLogger了。

回到源码的"51处",用log4jLogger创建一个适配器=>slf4j的Logger对象,并保存于org.slf4j.impl.Log4jLoggerFactory的ConcurrentHashMap对象loggerMap,下次则直接使用,不必再new。
public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements LocationAwareLogger, Serializable {    private static final long serialVersionUID = 6182834493563598289L;    final transient org.apache.log4j.Logger logger;    /**     * Following the pattern discussed in pages 162 through 168 of "The complete     * log4j manual".     */    final static String FQCN = Log4jLoggerAdapter.class.getName();    // Does the log4j version in use recognize the TRACE level?    // The trace level was introduced in log4j 1.2.12.    final boolean traceCapable;    // WARN: Log4jLoggerAdapter constructor should have only package access so    // that    // only Log4jLoggerFactory be able to create one.    Log4jLoggerAdapter(org.apache.log4j.Logger logger) {        this.logger = logger;        this.name = logger.getName();        traceCapable = isTraceCapable();    }    //其他省略...}
刚开始new出Log4jLoggerAdapter对象newInstance时,它包含的org.apache.log4j.Logger logger的相关属性:
{aai = null;additve = true;level = null;parent = org.apache.log4j.spi.RootLogger;repository = org.apache.log4j.Hierarchy;resourceBundle = null;}
看看Category的callAppenders方法:
public void callAppenders(LoggingEvent event) {    int writes = 0;    for(Category c = this; c != null; c=c.parent) {      // Protected against simultaneous call to addAppender, removeAppender,...      synchronized(c) {    if(c.aai != null) { //11处      writes += c.aai.appendLoopOnAppenders(event);    }    if(!c.additive) {      break;    }      }    }    if(writes == 0) {      repository.emitNoAppenderWarning(this);    } }
对于11处,当前log4j.Logger的aai为null,会循环回上级parent,即RootLogger,而RootLogger的AppenderAttachableImpl aai不为空。
[org.apache.log4j.ConsoleAppender, org.apache.log4j.DailyRollingFileAppender]

其实重点看下log4j是如何初始化的,在这里我们按配置文件是log4j.properties来看。
比如log4j.properties内容:
log4j.rootLogger = info, stdout, Dlog4j.appender.stdout = org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout = org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern = %d{MM-dd HH:mm:ss}:%p(%L) %c - %m%nlog4j.appender.D = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.D.File = logs/stdout.loglog4j.appender.D.DatePattern = '.'yyyy-MM-ddlog4j.appender.D.Append = truelog4j.appender.D.Encoding=UTF-8log4j.appender.D.Threshold = INFOlog4j.appender.D.layout = org.apache.log4j.PatternLayoutlog4j.appender.D.layout.ConversionPattern = %d{MM-dd HH:mm:ss}:%p(%L) %c - %m%n
log4j.rootLogger的info是表示日志级别,stdout和D才表示日志需要输出到对应的Appender。

在上面LogManager的源码"62处",调用了OptionConverter.selectAndConfigure(...)方法来配置log4j,在这里调用了PropertyConfigurator.doConfigure方法
doConfigure方法会配置RootLogger的Appender:
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(); //12处      synchronized(root) {        parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);      }    }  }
在12处取到RootLogger,调用parseCategory方法将log4j.properties的配置Appender类,加入到RootLogger中,从上面的log4j.properties文件中,可以看出会加载两个Appender
org.apache.log4j.ConsoleAppender和org.apache.log4j.DailyRollingFileAppender。

所以又回到Category的callAppenders方法的11处
if(c.aai != null) {  writes += c.aai.appendLoopOnAppenders(event);}appendLoopOnAppenders方法:public int appendLoopOnAppenders(LoggingEvent event) {    int size = 0;    Appender appender;    if(appenderList != null) {      size = appenderList.size();      for(int i = 0; i < size; i++) {        appender = (Appender) appenderList.elementAt(i);        appender.doAppend(event); //13处      }    }        return size;}
打印日志时,会循环调用Appender,即是调用log4j.properties配置的那两个Appender
在13处,调用13处会调用Appender的doAppend方法,AppenderSkeleton是Appender的实现抽象类,
所以会先调用AppenderSkeleton的doAppend方法:
public abstract class AppenderSkeleton implements Appender, OptionHandler {  public synchronized void doAppend(LoggingEvent event) {    if(closed) {      LogLog.error("Attempted to append to closed appender named ["+name+"].");      return;    }        if(!isAsSevereAsThreshold(event.getLevel())) {      return;    }    Filter f = this.headFilter;        FILTER_LOOP:    while(f != null) { //14处      switch(f.decide(event)) {      case Filter.DENY: return;      case Filter.ACCEPT: break FILTER_LOOP;      case Filter.NEUTRAL: f = f.getNext();      }    }        this.append(event); //15处      }  abstract protected void append(LoggingEvent event);}
在上面AppenderSkeleton的doAppend方法中的"14处",会查找链条Filter(相当于责任链模式),根据Filter的返回状态:
1.Filter.DENY,不打印日志,方法结束;
2.Filter.ACCEPT,可以打印日志,退出循环;
3.Filter.NEUTRAL,中立状态,交给下一个Filter来决定。
最后"15处"会调用append抽象方法,由AppenderSkeleton的继承类来实现append方法。

打印到控制台:
public class ConsoleAppender extends WriterAppender {}

打印到文件(日期滚动):
public class DailyRollingFileAppender extends FileAppender {}
public class FileAppender extends WriterAppender {}
public class WriterAppender extends AppenderSkeleton {}

如果使用异步日志,log4j的配置文件需要使用log4j.xml,异步日志使用的Appender是org.apache.log4j.AsyncAppender
public class AsyncAppender extends AppenderSkeleton implements AppenderAttachable {}
可以看出异步日志Appender也是继承AppenderSkeleton的,
下面我自己重写了异步日志的Appender:
https://github.com/jjavaboy/lam-nio/blob/master/lam-nio-core/src/main/java/lam/log/LamScheduleAsyncAppender.java
log4j.xml配置文件:
https://github.com/jjavaboy/lam-nio/blob/master/lam-schedule/src/main/resources/log4j.xml
原创粉丝点击