#0 系列目录#html
#1 Slf4j# ##1.1 介绍## SLF4J,即简单日志门面(Simple Logging Facade for Java)。从设计模式的角度考虑,它是用来在log和代码层之间起到门面的做用
。对用户来讲只要使用slf4j提供的接口,便可隐藏日志的具体实现。这与jdbc和类似。使用jdbc也就避免了不一样的具体数据库。使用了slf4j能够对客户端应用解耦。由于当咱们在代码实现中引入log日志的时候,用的是接口,因此能够实时的更具状况来调换具体的日志实现类。这就是slf4j的做用。java
SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类
。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录便可,最终日志的格式、记录级别、输出方式等经过具体日志系统的配置来实现,所以能够在应用中灵活切换日志系统。数据库
配置SLF4J是很是简单的一件事,只要将与你打算使用的日志系统对应的jar包加入到项目中,SLF4J就会自动选择使用你加入的日志系统。
apache
##1.2 简单使用##设计模式
/** * Slf4j 日志门面接口 Test * @author taomk * @version 1.0 * @since 15-10-15 下午3:39 */ public class Slf4jFacadeTest { private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class); public static void main(String[] args){ if(logger.isDebugEnabled()){ logger.debug("slf4j-log4j debug message"); } if(logger.isInfoEnabled()){ logger.debug("slf4j-log4j info message"); } if(logger.isTraceEnabled()){ logger.debug("slf4j-log4j trace message"); } } }
上述Logger接口、LoggerFactory类都是slf4j本身定义的。那么,SLF4J是怎么实现日志绑定的?api
##1.3 日志绑定##性能优化
private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); }
上述获取Log的过程大体分红2个阶段:服务器
下面来详细说明:架构
又能够分红3个过程
:org/slf4j/impl/StaticLoggerBinder.class
类:LoggerFactory.javaprivate static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; private static Set<URL> findPossibleStaticLoggerBinderPathSet() { // use Set instead of list in order to deal with bug #138 // LinkedHashSet appropriate here because it preserves insertion order during iteration Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); Enumeration<URL> paths; if (loggerFactoryClassLoader == null) { paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); } else { paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); } while (paths.hasMoreElements()) { URL path = (URL) paths.nextElement(); staticLoggerBinderPathSet.add(path); } } catch (IOException ioe) { Util.report("Error getting resources from path", ioe); } return staticLoggerBinderPathSet; }
若是找到多个,则输出 Class path contains multiple SLF4J bindings,表示有多个日志实现与slf4j进行了绑定
。下面看下当出现多个StaticLoggerBinder的时候的输出日志(简化了一些内容):LoggerFactory.javaapp
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [slf4j-jdk14-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
“随机选取"
一个StaticLoggerBinder.class来建立一个单例:private final static void bind() { try { Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); // 打印搜索到的全部StaticLoggerBinder日志 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); // the next line does the binding 随机选取绑定 StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; // 打印最终实际绑定StaticLoggerBinder日志 reportActualBinding(staticLoggerBinderPathSet); fixSubstitutedLoggers(); } catch (NoClassDefFoundError ncde) { String msg = ncde.getMessage(); if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); Util.report("Defaulting to no-operation (NOP) logger implementation"); Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); } else { failedBinding(ncde); throw ncde; } } catch (java.lang.NoSuchMethodError nsme) { String msg = nsme.getMessage(); if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) { INITIALIZATION_STATE = FAILED_INITIALIZATION; Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); Util.report("Your binding is version 1.5.5 or earlier."); Util.report("Upgrade your binding to version 1.6.x."); } throw nsme; } catch (Exception e) { failedBinding(e); throw new IllegalStateException("Unexpected initialization failure", e); } }
返回一个ILoggerFactory实例
:LoggerFactory.javapublic static ILoggerFactory getILoggerFactory() { if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: // 返回绑定 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://bugzilla.slf4j.org/show_bug.cgi?id=106 return TEMP_FACTORY; } throw new IllegalStateException("Unreachable code"); }
因此slf4j与其余实际的日志框架的集成jar包中,都会含有这样的一个org/slf4j/impl/StaticLoggerBinder.class类文件
,而且提供一个ILoggerFactory的实现。
这就要看具体的ILoggerFactory类型了
,下面与Log4j集成来详细说明。#2 Log4j介绍# Apache的一个开放源代码项目,经过使用Log4j,咱们能够控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等
;用户也能够控制每一条日志的输出格式
;经过定义每一条日志信息的级别,用户可以更加细致地控制日志的生成过程
。这些能够经过一个配置文件来灵活地进行配置,而不须要修改程序代码。具体详细介绍,参见Log4j架构分析与实战。
#3 Slf4j与Log4j集成# ##3.1 Maven依赖##
<!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <!-- slf4j-log4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
##3.2 使用案例##
log4j.rootLogger = debug, console log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %m%n
配置文件的详细内容参见Log4J配置文件详解。
private static Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class); public static void main(String[] args){ if(logger.isDebugEnabled()){ logger.debug("slf4j-log4j debug message"); } if(logger.isInfoEnabled()){ logger.info("slf4j-log4j info message"); } if(logger.isTraceEnabled()){ logger.trace("slf4j-log4j trace message"); } }
slf4j: Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class); log4j: Logger logger=Logger.getLogger(Log4jTest.class);
slf4j的Logger是slf4j定义的接口,而log4j的Logger是类。LoggerFactory是slf4j本身的类。
##3.3 原理分析## 先来看下slf4j-log4j12包中的内容:
org/slf4j/impl/StaticLoggerBinder.class类。
Log4jLoggerFactory。
来看下具体过程:
“org/slf4j/impl/StaticLoggerBinder.class"
这样的类的url,而后就找到了slf4j-log4j12包中的StaticLoggerBinder。并建立出ILoggerFactory
,源码以下:StaticLoggerBinder.getSingleton().getLoggerFactory();
以slf4j-log4j12中的StaticLoggerBinder为例,建立出的ILoggerFactory为Log4jLoggerFactory。
org.apache.log4j.Logger log4jLogger; if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) log4jLogger = LogManager.getRootLogger(); else log4jLogger = LogManager.getLogger(name); Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
引起log4j1的加载配置文件,而后初始化
,最后返回一个org.apache.log4j.Logger log4jLogger,参见log4j1原生开发。log4jLogger封装成Log4jLoggerAdapter,而Log4jLoggerAdapter是实现了slf4j的接口
,因此咱们使用的slf4j的Logger接口实例(这里即Log4jLoggerAdapter)都会委托给内部的org.apache.log4j.Logger实例。#4 Slf4j与Log4j源码分析# ##4.1 Slf4j初始化##
当前类类加载时或者显示得执行调用LoggerFactory.getLogger()方法时
,触发Slf4j初始化,并绑定具体日志:Slf4jFacadeTest.javaprivate static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
public static Logger getLogger(String name) { // 1. 初始化LoggerFactory,绑定具体日志,得到具体日志的LoggerFactory。 ILoggerFactory iLoggerFactory = getILoggerFactory(); // 2. 根据具体日志的LoggerFactory,触发具体日志的初始化并得到具体日志的Logger对象; return iLoggerFactory.getLogger(name); }
public static ILoggerFactory getILoggerFactory() { // 1. 是否已经初始化了,不然进行初始化 if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: // 2. 成功初始化,则直接得到具体日志的LoggerFactory 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://bugzilla.slf4j.org/show_bug.cgi?id=106 return TEMP_FACTORY; } throw new IllegalStateException("Unreachable code"); }
private final static void performInitialization() { // 1. 绑定具体的日志实现 bind(); if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { versionSanityCheck(); } }
private final static void bind() { try { // 1. 扫描查找“org/slf4j/impl/StaticLoggerBinder.class” Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); // 2. 打印找到多个“org/slf4j/impl/StaticLoggerBinder.class”的日志 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); // the next line does the binding 随机选择绑定,类加载器随机选择。 StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; // 3. 打印绑定具体org/slf4j/impl/StaticLoggerBinder.class的日志 reportActualBinding(staticLoggerBinderPathSet); fixSubstitutedLoggers(); } catch (NoClassDefFoundError ncde) { String msg = ncde.getMessage(); if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); Util.report("Defaulting to no-operation (NOP) logger implementation"); Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); } else { failedBinding(ncde); throw ncde; } } catch (java.lang.NoSuchMethodError nsme) { String msg = nsme.getMessage(); if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) { INITIALIZATION_STATE = FAILED_INITIALIZATION; Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); Util.report("Your binding is version 1.5.5 or earlier."); Util.report("Upgrade your binding to version 1.6.x."); } throw nsme; } catch (Exception e) { failedBinding(e); throw new IllegalStateException("Unexpected initialization failure", e); } }
private static Set<URL> findPossibleStaticLoggerBinderPathSet() { // use Set instead of list in order to deal with bug #138 // LinkedHashSet appropriate here because it preserves insertion order during iteration Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); Enumeration<URL> paths; if (loggerFactoryClassLoader == null) { // 1. 扫包查找“org/slf4j/impl/StaticLoggerBinder.class” paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); } else { paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); } while (paths.hasMoreElements()) { URL path = (URL) paths.nextElement(); staticLoggerBinderPathSet.add(path); } } catch (IOException ioe) { Util.report("Error getting resources from path", ioe); } return staticLoggerBinderPathSet; }
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); public static final StaticLoggerBinder getSingleton() { // 1. 返回StaticLoggerBinder实例 return SINGLETON; } private StaticLoggerBinder() { // 2. StaticLoggerBinder初始化 loggerFactory = new Log4jLoggerFactory(); try { Level level = Level.TRACE; }cache (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"); } }
以上就是Slf4j初始化过程的源代码,其初始化过程就是绑定具体日志实现
;
##4.2 Log4j初始化## 这里只在源码层级作分析,不想看源码,可直接参考具体详细流程,请参见Log4j初始化分析。
初始化时机:前一小节Slf4j已提到,在iLoggerFactory.getLogger(name)时触发Log4j初始化
。iLoggerFactory具体类型为:Log4jLoggerFactory。
初始化步骤:
public Logger getLogger(String name) { Logger slf4jLogger = null; // protect against concurrent access of loggerMap synchronized (this) { slf4jLogger = (Logger) loggerMap.get(name); if (slf4jLogger == null) { // 1. 获取Logej具体的Logger对象。 org.apache.log4j.Logger log4jLogger; if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) { log4jLogger = LogManager.getRootLogger(); } else { log4jLogger = LogManager.getLogger(name); } slf4jLogger = new Log4jLoggerAdapter(log4jLogger); loggerMap.put(name, slf4jLogger); } } return slf4jLogger; }
static { // By default we use a DefaultRepositorySelector which always returns 'h'. // 1. 初始化Logger仓库,并添加一个RootLogger实例,默认日志级别为DEBUG。 Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); repositorySelector = new DefaultRepositorySelector(h); /** Search for the properties file log4j.properties in the CLASSPATH. */ String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null); // 2. 检查系统属性log4j.defaultInitOverride,若是该属性被设置为false,则执行初始化;不然(只要不是false,不管是什么值,甚至没有值,都是不然),跳过初始化。 // 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)) { String configurationOptionStr = OptionConverter.getSystemProperty( DEFAULT_CONFIGURATION_KEY, null); String configuratorClassName = OptionConverter.getSystemProperty( CONFIGURATOR_CLASS_KEY, null); URL url = null; // 3. 把系统属性log4j.configuration的值赋给变量resource。若是该系统变量没有被定义,则把resource赋值为"log4j.properties"。注意:在apache的log4j文档中建议使用定义log4j.configuration系统属性的方法来设置默认的初始化文件是一个好方法。 // 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) { url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE); if(url == null) { url = Loader.getResource(DEFAULT_CONFIGURATION_FILE); } } else { try { // 4. 试图把resource变量转化成为一个URL对象url。若是通常的转化方法行不通,就调用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)方法来完成转化。 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 { // 5. 若是url以".xml"结尾,则调用方法DOMConfigurator.configure(url)来完成初始化;不然,则调用方法PropertyConfigurator.configure(url)来完成初始化。若是url指定的资源不能被得到,则跳出初始化过程。 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."); } }
public static void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) { Configurator configurator = null; String filename = url.getFile(); // 1. 若是url以".xml"结尾,则调用方法DOMConfigurator.configure(url)来完成初始化;不然,则调用方法PropertyConfigurator.configure(url)来完成初始化。若是url指定的资源不能被得到,则跳出初始化过程。 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); }
##4.3 Log4j输出日志## 这里只在源码层级作分析,不想看源码,可直接参考具体详细流程,请参见Log4j输出日志。
Logger继承自Category.java
)public void info(Object message) { // 1. 根据全局日志等级threshold进行判断,若是日志等级低于threshold,不输出日志。 if(repository.isDisabled(Level.INFO_INT)) return; // 2. 根据当前logger配置的日志等级level进行判断,若是日志等级低于当前logger配置的日志等级,不输出日志。 if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) // 3. 将日志信息封装成LoggingEvent对象。 forcedLog(FQCN, Level.INFO, message, null); }
protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) { // 1. 将LoggingEvent对象分发给全部的Appender。 callAppenders(new LoggingEvent(fqcn, this, level, message, t)); } 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) { // 2. 将LoggingEvent对象分发给全部的Appender。 writes += c.aai.appendLoopOnAppenders(event); } if(!c.additive) { break; } } } if(writes == 0) { repository.emitNoAppenderWarning(this); } } 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); } } return size; }
Filter处理和日志信息格式化
。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处理 Filter f = this.headFilter; FILTER_LOOP: while (f != null) { // 1. 有三种返回值 DENY、ACCEPT、NEUTRAL,DENY表示丢弃当前日志信息,ACCEPT表示输出当前日志信息,NEUTRAL表示继续下一个Filter。Filter只能在XML配置文件中使用,Properties文件中不支持。 switch (f.decide(event)) { case Filter.DENY: return; case Filter.ACCEPT: break FILTER_LOOP; case Filter.NEUTRAL: f = f.getNext(); } } this.append(event); } public void append(LoggingEvent event) { // Reminder: the nesting of calls is: // // doAppend() // - check threshold // - filter // - append(); // - checkEntryConditions(); // - subAppend(); if(!checkEntryConditions()) { return; } subAppend(event); } protected void subAppend(LoggingEvent event) { // 2. 日志信息格式化:对日志进行格式化处理。 this.qw.write(this.layout.format(event)); if (layout.ignoresThrowable()) { String[] s = event.getThrowableStrRep(); if (s != null) { int len = s.length; for (int i = 0; i < len; i++) { this.qw.write(s[i]); this.qw.write(Layout.LINE_SEP); } } } if (shouldFlush(event)) { // 3. 将日志信息输出至目的地(文件、数据库或网格) this.qw.flush(); } }