slf4j和log4j的源码及异步日志
来源:互联网 发布:scarsong知乎 编辑:程序博客网 时间:2024/06/11 00:12
我们在项目中,一般这样使用slf4j来记录日志:
看回1处的getILoggerFactory()方法,再调用getLogget方法,知道是使用工厂模式来创建Logger对象。
下面源码StaticLoggerBinder构造方法中new了工厂对象Log4jLoggerFactory
对源码的“5处”,其中LogManager先get到log4j的Logger log4jLogger对象
看看LogManager的getLogger方法里面其实调用了getLoggerRepository(),取得LoggerRepository对象再getLogger(name)得到Logger对象的。
可以先看看getLoggerRepository()方法
7处的repositorySelector在LogManager类加载时的static语块已经new了一个DefaultRepositorySelector对象,即6处,并设置了LoggerRepository-Hierarchy对象,设置日志的DEBUG级别
去看看61处new一个Hierarchy对象:
在11处updateParents方法:
到处,就算是已经创建好log4j的Logger对象log4jLogger了。
回到源码的"51处",用log4jLogger创建一个适配器=>slf4j的Logger对象,并保存于org.slf4j.impl.Log4jLoggerFactory的ConcurrentHashMap对象loggerMap,下次则直接使用,不必再new。
[org.apache.log4j.ConsoleAppender, org.apache.log4j.DailyRollingFileAppender]
其实重点看下log4j是如何初始化的,在这里我们按配置文件是log4j.properties来看。
比如log4j.properties内容:
在上面LogManager的源码"62处",调用了OptionConverter.selectAndConfigure(...)方法来配置log4j,在这里调用了PropertyConfigurator.doConfigure方法
doConfigure方法会配置RootLogger的Appender:
org.apache.log4j.ConsoleAppender和org.apache.log4j.DailyRollingFileAppender。
所以又回到Category的callAppenders方法的11处
在13处,调用13处会调用Appender的doAppend方法,AppenderSkeleton是Appender的实现抽象类,
所以会先调用AppenderSkeleton的doAppend方法:
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
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%nlog4j.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
- slf4j和log4j的源码及异步日志
- java日志-slf4j和log4j及slf4j和logback
- Log4J和slf4j的debug日志问题
- log4j 和 slf4j 日志框架
- SLF4J+ Log4j 的日志组合
- Java日志 - slf4j和log4j合用的配置(Maven)
- log4j异步日志的配置和测试
- log4j和slf4j日志框架入门
- log4j和slf4j日志框架入门
- 使用slf4j和log4j记录日志
- 使用slf4j和Log4j构建日志
- 日志记录方法---SLF4J和Log4j
- log4j 和slf4j的比较
- log4j 和slf4j的比较
- log4j 和slf4j的比较
- log4j 和slf4j的比较
- log4j 和slf4j的比较
- log4j和slf4j的使用
- 再就业之前,整理整理人生轨迹
- 数据库到底哪家强?
- C#使用特性自动保存属性值
- 【集训Day4 动态规划】轮船问题
- NYOJ 1249 物资调度(DFS+剪枝)
- slf4j和log4j的源码及异步日志
- Xmlhttprequest请求跨域
- C++中字符串处理函数
- git init 与 git init --bare 的区别
- App登陆java后台处理和用户权限验证
- java中的变量(成员变量、本地变量、类变量)
- 第九篇:Bug管理系统UML2.0建模实例(一)
- 对中国IT行业的拙见
- HDU 2276 Kiki & Little Kiki 2