#0 系列目录#
#1 Slf4j#
##1.1 介绍##
SLF4J,即简单日志门面(Simple Logging Facade for Java)。从设计模式的角度考虑,它是用来在log和代码层之间起到门面的作用。对用户来说只要使用slf4j提供的接口,即可隐藏日志的具体实现。这与jdbc和相似。使用jdbc也就避免了不同的具体数据库。使用了slf4j可以对客户端应用解耦。因为当我们在代码实现中引入log日志的时候,用的是接口,所以可以实时的更具情况来调换具体的日志实现类。这就是slf4j的作用。
SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
配置SLF4J是非常简单的一件事,只要将与你打算使用的日志系统对应的jar包加入到项目中,SLF4J就会自动选择使用你加入的日志系统。
##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是怎么实现日志绑定的?
##1.3 日志绑定##
在应用中,通过LoggerFactory类的静态getLogger()获取logger,代码如下:
private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
LoggerFactory.getLogger(Slf4jFacadeTest.class)方法的源码如下:LoggerFactory.java
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
上述获取Log的过程大致分成2个阶段:
获取ILoggerFactory的过程 (从字面上理解就是生产Logger的工厂);
根据ILoggerFactory获取Logger的过程;
下面来详细说明:
1 获取ILoggerFactory的过程,又可以分成3个过程:
1.1 从类路径中寻找org/slf4j/impl/StaticLoggerBinder.class类:LoggerFactory.java
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
private static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration 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.java
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]
1.2 “随机选取"一个StaticLoggerBinder.class来创建一个单例:
private final static void bind() {
try {
Set 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);
}
}
1.3 根据上述创建的StaticLoggerBinder单例,返回一个ILoggerFactory实例:LoggerFactory.java
public 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的实现。
2 根据ILoggerFactory获取Logger的过程:这就要看具体的ILoggerFactory类型了,下面与Log4j集成来详细说明。
#2 Log4j介绍#
Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;用户也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个配置文件来灵活地进行配置,而不需要修改程序代码。具体详细介绍,参见Log4j架构分析与实战。
#3 Slf4j与Log4j集成#
##3.1 Maven依赖##
org.slf4j
slf4j-api
1.7.12
org.slf4j
slf4j-log4j12
1.7.12
log4j
log4j
1.2.17
##3.2 使用案例##
第一步:编写log4j.properties配置文件,放到类路径下
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");
}
}
补充说明:
1 配置文件同样可以随意放置,如log4j1原生方式加载配置文件的方式log4j1原生开发
2 注意两者方式的不同:
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类。
该StaticLoggerBinder返回的ILoggerFactory类型将会是Log4jLoggerFactory。
Log4jLoggerAdapter就是实现了slf4j定义的Logger接口。
来看下具体过程:
1 获取对应的ILoggerFactory:从上面的slf4j的原理中我们知道:ILoggerFactory是由StaticLoggerBinder来创建出来的,所以可以简单分成2个过程:
1.1 第一个过程:slf4j寻找绑定类StaticLoggerBinder:使用ClassLoader来加载“org/slf4j/impl/StaticLoggerBinder.class"这样的类的url,然后就找到了slf4j-log4j12包中的StaticLoggerBinder。
1.2 第二个过程:创建出StaticLoggerBinder实例,并创建出ILoggerFactory,源码如下:
StaticLoggerBinder.getSingleton().getLoggerFactory();
以slf4j-log4j12中的StaticLoggerBinder为例,创建出的ILoggerFactory为Log4jLoggerFactory。
2 根据ILoggerFactory获取Logger的过程,来看下Log4jLoggerFactory是如何返回一个slf4j定义的Logger接口的实例的,源码如下:
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);
2.1 我们可以看到是通过log4j1的原生方式,即使用log4j1的LogManager来获取,引发log4j1的加载配置文件,然后初始化,最后返回一个org.apache.log4j.Logger log4jLogger,参见log4j1原生开发。
2.2 将上述的org.apache.log4j.Logger log4jLogger封装成Log4jLoggerAdapter,而Log4jLoggerAdapter是实现了slf4j的接口,所以我们使用的slf4j的Logger接口实例(这里即Log4jLoggerAdapter)都会委托给内部的org.apache.log4j.Logger实例。
#4 Slf4j与Log4j源码分析#
##4.1 Slf4j初始化##
初始化时机:下面这行代码,会在当前类类加载时或者显示得执行调用LoggerFactory.getLogger()方法时,触发Slf4j初始化,并绑定具体日志:Slf4jFacadeTest.java
private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
初始化步骤:
源码1:LoggerFactory.java
public static Logger getLogger(String name) {
// 1. 初始化LoggerFactory,绑定具体日志,获得具体日志的LoggerFactory。
ILoggerFactory iLoggerFactory = getILoggerFactory();
// 2. 根据具体日志的LoggerFactory,触发具体日志的初始化并获得具体日志的Logger对象;
return iLoggerFactory.getLogger(name);
}
源码2:LoggerFactory.java
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");
}
源码3:LoggerFactory.java
private final static void performInitialization() {
// 1. 绑定具体的日志实现
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
源码4:LoggerFactory.java
private final static void bind() {
try {
// 1. 扫描查找“org/slf4j/impl/StaticLoggerBinder.class”
Set 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);
}
}
源码5:LoggerFactory.java
private static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration 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;
}
源码6:slf4j-log4j-1.6.2.jar中StaticLoggerBinder.java
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。
初始化步骤:
源码1:Log4jLoggerFactory.java
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;
}
源码2:LogManager.java静态代码块初始化Log4j
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.");
}
}
源码3:OptionConverter.java
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输出日志。
预处理: 当调用Log4j的方法(如:debug(String, Throwable)、info(String, Throwable))输出日志时,首先对日志信息进行预处理。
源码1:Category.java(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);
}
源码2:Category.java
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处理和日志信息格式化。
源码1:
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();
}
}
若想了解Log4j的日志异步输出实现,请参见Log4j性能优化。