slf4j和log4j的源码及异步日志

我们在项目中,一般这样使用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 in
protected 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, D

log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d{MM-dd HH:mm:ss}:%p(%L) %c - %m%n

log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/stdout.log
log4j.appender.D.DatePattern = '.'yyyy-MM-dd
log4j.appender.D.Append = true
log4j.appender.D.Encoding=UTF-8
log4j.appender.D.Threshold = INFO
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值