日志-log4j2日志框架源码 正文一. Logger获取通常使用Slf4j的LoggerFactory获取Log4j2的Logger的代码如下所示。private static final Logger logger_log4j2 LoggerFactory.getLogger(LearnLog4j2.class);跟进LoggerFactory的getLogger()方法如下所示。public static Logger getLogger(Class? clazz) { Logger logger getLogger(clazz.getName()); ...... return logger; } public static Logger getLogger(String name) { // 使用Log4j2日志框架时这里得到的ILoggerFactory实际为Log4jLoggerFactory ILoggerFactory iLoggerFactory getILoggerFactory(); // 通过Log4jLoggerFactory的getLogger()方法获取Logger return iLoggerFactory.getLogger(name); }已知LoggerFactory的getILoggerFactory()方法会自动绑定实际使用的日志框架所以这里getILoggerFactory()方法会返回Log4jLoggerFactory其是Log4j2提供的ILoggerFactory接口的实现类。Log4jLoggerFactory继承于AbstractLoggerAdapter所以继续会调用到AbstractLoggerAdapter的getLogger()方法如下所示。public L getLogger(final String name) { // 获取到LoggerContext final LoggerContext context getContext(); // 拿到当前LoggerContext关联的Logger的map final ConcurrentMapString, L loggers getLoggersInContext(context); // 根据name从Logger的map中拿到Logger final L logger loggers.get(name); if (logger ! null) { // 拿到的Logger不为空则直接返回 return logger; } // 拿到的Logger为空则新建一个Logger并存储到Logger的map中 loggers.putIfAbsent(name, newLogger(name, context)); // 然后返回新建的Logger return loggers.get(name); }在AbstractLoggerAdapter有一个叫做registry的字段其表示如下。protected final MapLoggerContext, ConcurrentMapString, L registry new ConcurrentHashMap();即一个LoggerContext会对应一个Logger的mapAbstractLoggerAdapter的getLoggersInContext()方法会将这个map初始化并获取出来最后从map中根据name字段获取Logger。LoggerContext是Log4j2中的锚点其维护了应用请求的所有Logger的列表和Log4j2的配置信息ConfigurationLoggerContext的获取将在下一小节进行分析现在继续跟进Logger的创建即Log4jLoggerFactory提供的newLogger()方法如下所示。protected Logger newLogger(final String name, final LoggerContext context) { final String key Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name; // 创建出来的Logger实际为Log4jLogger return new Log4jLogger(validateContext(context).getLogger(key), name); }在Log4jLoggerFactory的newLogger()方法中创建出来的Logger实际为Log4jLogger同时validateContext()方法仅会校验一下LoggerContext的全限定名是否为org.apache.logging.slf4j.SLF4JLoggerContext如果不是则校验通过否则报错校验通过后会返回这个被校验的LoggerContext所以继续看LoggerContext的getLogger()方法的实现如下所示。public Logger getLogger(final String name) { return getLogger(name, null); } public Logger getLogger(final String name, final MessageFactory messageFactory) { Logger logger loggerRegistry.getLogger(name, messageFactory); if (logger ! null) { AbstractLogger.checkMessageFactory(logger, messageFactory); return logger; } // Logger在这里实例化 logger newInstance(this, name, messageFactory); loggerRegistry.putIfAbsent(name, messageFactory, logger); return loggerRegistry.getLogger(name, messageFactory); }继续跟进LoggerContext的newInstance()方法如下所示。protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { return new Logger(ctx, name, messageFactory); } protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) { super(name, messageFactory); this.context context; // 创建PrivateConfigPrivateConfig是Logger与其配置之间的绑定 privateConfig new PrivateConfig(context.getConfiguration(), this); }在LoggerContext的newInstance()方法中会调用Logger的构造函数在Logger的构造函数中会为Logger创建一个PrivateConfig对象该对象的官方注释直译过来如下所示。PrivateConfig是Logger与其配置之间的绑定。PrivateConfig内部持有一个Configuration对象和一个LoggerConfig对象其中Configuration对象从LoggerContext拿到LoggerConfig对象从Configuration中拿到Logger会将打印日志的任务委派给其持有的PrivateConfig中的LoggerConfig对象所以真正打印日志的对象是LoggerConfig。下面继续跟进如何从Configuration对象中获取到LoggerConfig由于是通过XML文件进行配置所以Configuration对象的实际类型是XmlConfiguration调用XmlConfiguration的getLoggerConfig()方法时会实际调用到其父类AbstractConfiguration的getLoggerConfig()方法实现如下所示。public LoggerConfig getLoggerConfig(final String loggerName) { // loggerConfigs是一个map结构key是Logger的namevalue是LoggerConfig LoggerConfig loggerConfig loggerConfigs.get(loggerName); if (loggerConfig ! null) { // 根据loggerName能够直接从map中获取到LoggerConfig那么直接返回这个LoggerConfig return loggerConfig; } // 如果获取不到那么循环的通过Logger的name找到父name的LoggerConfig // 比如loggerName为A.B.C.D且直接根据A.B.C.D从map中获取不到LoggerConfig // 那么会循环的依次以A.B.CA.BA从map中获取LoggerConfig String substr loggerName; while ((substr NameUtil.getSubName(substr)) ! null) { loggerConfig loggerConfigs.get(substr); if (loggerConfig ! null) { return loggerConfig; } } // 如果都获取不到LoggerConfig那么返回根LoggerConfig即root return root; }从Configuration对象中获取LoggerConfig时就是从Configuration对象持有的LoggerConfig的map中根据loggerName将LoggerConfig获取出来如果获取不到则依次以loggerName的父name去从map中获取如果都获取不到则会使用根LoggerConfig。下面以一个简单例子说明这个获取过程假如配置文件中Configuration标签下的Loggers标签配置如下所示。Loggers Logger namecom.lee levelINFO/ Root levelINFO appender-ref refMyConsole/ appender-ref refMyFile/ /Root /Loggers那么Configuration对象持有的loggerConfigs如下所示。那么一开始以com.lee.learn.log4j2.Log4j2Test去loggerConfigs获取LoggerConfig时会获取不到然后再以com.lee.learn.log4j2去获取还是获取不到依次直到以com.learn去获取此时会获取到。至此Logger的获取分析完毕由于在整个过程中出现了多个Logger下面以一个类图进行概括示意展示这些Logger之间的关联关系。简单做一个小结应用程序中最终获取到的日志打印器Logger就是org.apache.logging.slf4j.Log4jLoggerLog4jLogger会将打印日志的任务委派给其持有的org.apache.logging.log4j.core.Logger中的PrivateConfig而PrivateConfig又会将打印日志的任务委派给其持有的LoggerConfig所以实际执行打印任务的是LoggerConfig。同时每个日志打印器都有独属于自己的PrivateConfig而Configuration则是全局共享的。最后LoggerConfig还有一个成员变量为parent在LoggerConfig完成日志打印后还会判断是否让其parent也打印日志这个在第三节会详细进行说明。二. LoggerContext获取上一节中已知LoggerContext是Log4j2中的锚点其维护了应用请求的所有Logger的列表和Log4j2的配置信息Configuration。LoggerContext是在Log4jLoggerFactory的getContext()方法中进行获取的其实现如下所示。protected LoggerContext getContext() { final Class? anchor LogManager.getFactory().isClassLoaderDependent() ? StackLocatorUtil.getCallerClass(Log4jLoggerFactory.class, CALLER_PREDICATE) : null; LOGGER.trace(Log4jLoggerFactory.getContext() found anchor {}, anchor); // 调用到AbstractLoggerAdapter的getContext()方法 return anchor null ? LogManager.getContext(false) : getContext(anchor); } protected LoggerContext getContext(final Class? callerClass) { ClassLoader cl null; if (callerClass ! null) { // 拿到调用类的类加载器 cl callerClass.getClassLoader(); } if (cl null) { cl LoaderUtil.getThreadContextClassLoader(); } // 调用LogManager的getContext()方法来获取LoggerContext return LogManager.getContext(cl, false); }在Log4jLoggerFactory的getContext()方法中会调用到父类AbstractLoggerAdapter的getContext()方法然后最后会调用到LogManager的getContext()方法跟进如下所示。public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) { try { return factory.getContext(FQCN, loader, null, currentContext); } catch (final IllegalStateException ex) { LOGGER.warn(ex.getMessage() Using SimpleLogger); return new SimpleLoggerContextFactory() .getContext(FQCN, loader, null, currentContext); } }上述方法中的factory字段是LogManager持有的LoggerContextFactory静态成员变量在静态代码块中完成初始化其实际类型是Log4jContextFactory用于加载LoggerContext现在继续跟进Log4jContextFactory的getContext()方法如下所示。public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { // 使用selector选择得到一个LoggerContext final LoggerContext ctx selector.getContext(fqcn, loader, currentContext); if (externalContext ! null ctx.getExternalContext() null) { ctx.setExternalContext(externalContext); } // 第一次获取到的LoggerContext的state为INITIALIZED if (ctx.getState() LifeCycle.State.INITIALIZED) { // 调用LoggerContext的start()方法以启动LoggerContext // LoggerContext启动完毕后其状态会被设置为STARTED ctx.start(); } return ctx; }Log4jContextFactory的getContext()方法会首先使用其持有的selector来选择获取一个LoggerContext如果是第一次获取LoggerContext那么获取到的LoggerContext的state为INITIALIZED此时会调用LoggerContext的start()方法来启动LoggerContext启动完毕后LoggerContext的state会被设置为STARTED。public void start() { ...... if (configLock.tryLock()) { try { if (this.isInitialized() || this.isStopped()) { this.setStarting(); // 为LogContext设置配置信息 reconfigure(); if (this.configuration.isShutdownHookEnabled()) { setUpShutdownHook(); } this.setStarted(); } } finally { configLock.unlock(); } } ...... }LoggerContext的start()方法中很重要的就是为LoggerContext设置配置信息跟进reconfigure()方法如下所示。private void reconfigure(final URI configURI) { Object externalContext externalMap.get(EXTERNAL_CONTEXT_KEY); final ClassLoader cl ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; LOGGER.debug(Reconfiguration started for context[name{}] at URI {} ({}) with optional ClassLoader: {}, contextName, configURI, this, cl); // 找到配置文件并解析为Configuration对象 final Configuration instance ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl); if (instance null) { LOGGER.error(Reconfiguration failed: No configuration found for {} at {} in {}, contextName, configURI, cl); } else { // 为LoggerContext设置Configuration然后启动Configuration setConfiguration(instance); final String location configuration null ? ? : String.valueOf(configuration.getConfigurationSource()); LOGGER.debug(Reconfiguration complete for context[name{}] at URI {} ({}) with optional ClassLoader: {}, contextName, location, this, cl); } }reconfigure()方法中会先找到配置文件并解析为Configuration对象然后为LoggerContext设置Configuration在设置的过程中还会一并将Configuration启动起来而Configuration的启动实际就是将配置的AppenderLoggerLoggerConfig根LoggerLoggerConfig等都创建出来并启动。LoggerContext启动完成后就可以返回了。至此LoggerContext获取的流程分析完毕。下面对本节内容进行小结。LoggerContext是Log4j2中的锚点其维护了应用请求的所有Logger的列表和Log4j2的配置信息Configuration首次获取得到的LoggerContext的状态为INITIALIZED此时需要调用LoggerContext的start()方法来启动LoggerContextLoggerContext启动实际就是找到Log4j2配置文件并解析为Configuration然后将Configuration设置给LoggerContext在设置过程中还会将Configuration启动也就是将配置的AppenderLogger等创建出来如果有异步的AppenderLogger等还需要将这些异步组件的线程启动起来LoggerContext启动完毕后就可以返回了。三. Logger打印日志本小节对Logger打印一条日志的完整流程进行分析。正式分析前需要进行一些准备工作。首先给出下面一份Log4j2的配置文件如下所示。?xml version1.0 encodingUTF-8? !-- status用于设置log4j2自身日志的信息输出级别不配置时默认为OFF -- Configuration statusINFO Properties Property nameTEST_LOGGERcom.lee.learn.log4j2.Log4j2Test/Property /Properties !-- 全局过滤器 -- ContextFilter/ !-- Appenders用于定义日志输出到哪里其下常用三种子标签ConsoleFile和RollingFile -- !-- 通过定义Appender可以定义输出日志到哪里输出日志级别日志保存清理策略等 -- Appenders !-- Console表示控制台信息输出配置 -- Console nameMyConsole targetSYSTEM_OUT !-- ThresholdFilter用于定义过滤机制 -- !-- level属性定义日志过滤级别 -- !-- onMatchACCEPT表示保留level及以上级别的日志 -- !-- onMismatchDENY表示丢掉level以下级别的日志-- ThresholdFilter levelDEBUG onMatchACCEPT onMismatchDENY/ !-- PatternLayout用于定义日志的输出格式 -- PatternLayout pattern%msg%n/ /Console !-- 输出WARN级别及以上的日志信息到日志文件 -- RollingFile nameMyFile fileNamemylog.log filePatternmylog.log.%i ThresholdFilter levelWARN onMatchACCEPT onMismatchDENY / PatternLayout pattern%msg%n/ SizeBasedTriggeringPolicy size20M/ /RollingFile /Appenders Loggers !-- 根日志打印器 -- Root levelINFO Appender-ref refMyConsole/ Appender-ref refMyFile/ /Root !-- 自定义日志打印器 -- Logger name${TEST_LOGGER} levelINFO additivityfalse LoggerFilter/ Appender-ref refMyConsole AppenderRefFilter/ /Appender-ref Appender-ref refMyFile AppenderRefFilter/ /Appender-ref /Logger /Loggers /Configuration上述配置文件中为Context设置了一个全局的自定义过滤器ContextFilter为TEST_LOGGER设置了一个Logger级别的自定义过滤器LoggerFilter以及一个Appender-ref级别的自定义过滤器AppenderRefFilter这些自定义过滤器的实现如下。Plugin(name ContextFilter, category Node.CATEGORY, elementType Filter.ELEMENT_TYPE, printObject true) public class MyContextFilter extends AbstractFilter { public MyContextFilter() { super(); } PluginFactory public static MyContextFilter createFilter() { return new MyContextFilter(); } Override public Result filter(LogEvent event) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { return Result.ACCEPT; } } Plugin(name LoggerFilter, category Node.CATEGORY, elementType Filter.ELEMENT_TYPE, printObject true) public class MyLoggerFilter extends AbstractFilter { public MyLoggerFilter() { super(); } PluginFactory public static MyLoggerFilter createFilter() { return new MyLoggerFilter(); } Override public Result filter(LogEvent event) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { return Result.ACCEPT; } } Plugin(name AppenderRefFilter, category Node.CATEGORY, elementType Filter.ELEMENT_TYPE, printObject true) public class MyAppenderRefFilter extends AbstractFilter { public MyAppenderRefFilter() { super(); } PluginFactory public static MyAppenderRefFilter createFilter() { return new MyAppenderRefFilter(); } Override public Result filter(LogEvent event) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { return Result.ACCEPT; } Override public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { return Result.ACCEPT; } }同时还为MyConsole和MyFile这两个自定义Appender设置了Log4j2预置的过滤器ThresholdFilter至此准备工作完毕。现在开始分析一条日志的打印流程。已知使用Log4j2框架时打印日志的日志打印器类型为Log4jLogger以打印INFO级别日志为例看一下Log4jLogger的info()方法实现。public void info(final String format) { logger.logIfEnabled(FQCN, Level.INFO, null, format); }上述方法中的FQCN取值为打印日志的入口的日志打印器的全限定名这里为org.apache.logging.slf4j.Log4jLogger表示打印日志的入口的日志打印器是Log4jLogger后续可以用于去堆栈中匹配打印日志的代码行号。已知Log4jLogger持有一个类型为org.apache.logging.log4j.core.Logger的日志打印器打印日志会由Logger来完成继续看Logger实际是Logger父类AbstractLogger的logIfEnabled()方法如下所示。public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message) { if (isEnabled(level, marker, message)) { logMessage(fqcn, level, marker, message); } }在上述方法中首先会调用到Logger的isEnabled()方法来过滤日志在这里会使用到为Context配置的filter来过滤日志比如本小节一开始时在配置文件中为Context配置的ContextFilter而ContextFilter则是通过Logger中的PrivateConfig中的Configuration来拿到的。继续看Logger实际是Logger父类AbstractLogger的logMessage()方法如下所示。protected void logMessage(final String fqcn, final Level level, final Marker marker, final String message) { // 将日志打印内容包装为Message的实现类对象 // 例如只是简单打印一条字符串日志内容这里的Message为ReusableSimpleMessage // 例如打印的内容使用了占位符这里的Message为ReusableParameterizedMessage用于处理占位符 final Message msg messageFactory.newMessage(message); logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } private void logMessageSafely(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable throwable) { try { logMessageTrackRecursion(fqcn, level, marker, message, throwable); } finally { ReusableMessageFactory.release(message); } } private void logMessageTrackRecursion(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable throwable) { try { incrementRecursionDepth(); tryLogMessage(fqcn, getLocation(fqcn), level, marker, message, throwable); } finally { decrementRecursionDepth(); } } private void tryLogMessage(final String fqcn, final StackTraceElement location, final Level level, final Marker marker, final Message message, final Throwable throwable) { try { // 在这里调用到Logger实现的log()方法 log(level, marker, fqcn, location, message, throwable); } catch (final Throwable t) { handleLogMessageException(t, fqcn, message); } }Logger父类AbstractLogger的logMessage()方法的整体的一个调用如上所示其中值得关注的是需要打印的日志被包装成了Message接口的子类例如打印的简单字符串内容会被包装成ReusableSimpleMessage对象使用了占位符的打印内容则会被包装成ReusableParameterizedMessage对象。Logger父类AbstractLogger的logMessage()方法一路调用下来最后会调用到其子类Logger实现的log()方法如下所示。protected void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location, final Message message, final Throwable throwable) { // 从Logger中的PrivateConfig中的LoggerConfig中获取到ReliabilityStrategy其实际类型为AwaitCompletionReliabilityStrategy // ReliabilityStrategy会将待打印的Message传递给对应的Appender进行日志打印 final ReliabilityStrategy strategy privateConfig.loggerConfig.getReliabilityStrategy(); if (strategy instanceof LocationAwareReliabilityStrategy) { ((LocationAwareReliabilityStrategy) strategy).log(this, getName(), fqcn, location, marker, level, message, throwable); } else { strategy.log(this, getName(), fqcn, marker, level, message, throwable); } }在Logger实现的log()方法中会从Logger中的PrivateConfig中的LoggerConfig中获取到AwaitCompletionReliabilityStrategy同时AwaitCompletionReliabilityStrategy也持有LoggerConfig的引用。继续看AwaitCompletionReliabilityStrategy的log()方法如下所示。public void log(final SupplierLoggerConfig reconfigured, final String loggerName, final String fqcn, final StackTraceElement location, final Marker marker, final Level level, final Message data, final Throwable t) { // 拿到LoggerConfig final LoggerConfig config getActiveLoggerConfig(reconfigured); try { // 打印日志是交由LoggerConfig完成 config.log(loggerName, fqcn, location, marker, level, data, t); } finally { config.getReliabilityStrategy().afterLogEvent(); } }由上可知打印日志是由LoggerConfig来完成继续跟进LoggerConfig的log()方法如下所示。public void log(final String loggerName, final String fqcn, final StackTraceElement location, final Marker marker, final Level level, final Message data, final Throwable t) { final ListProperty props getProperties(loggerName, fqcn, marker, level, data, t); // 将本次日志打印包装成一个打印事件LogEvent final LogEvent logEvent logEventFactory.createEvent(loggerName, marker, fqcn, location, level, data, props, t); try { // 这里第二个参数用于同步和异步打印的判断 log(logEvent, LoggerConfigPredicate.ALL); } finally { ReusableLogEventFactory.release(logEvent); } } protected void log(final LogEvent event, final LoggerConfigPredicate predicate) { // 在这里使用Logger级别过滤器进行过滤 if (!isFiltered(event)) { processLogEvent(event, predicate); } } private void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) { // 因为是同步打印日志isIncludeLocation()方法会返回true表示需要打印行号信息 // 如果是异步打印日志则需要在AsyncLogger标签中配置includeLocationtrueisIncludeLocation()方法才会返回true event.setIncludeLocation(isIncludeLocation()); // 同步和异步打印的判断 if (predicate.allow(this)) { callAppenders(event); } // 如果当前日志打印器有父日志打印器且additivity属性为true则调用父日志打印器来打印日志内容 logParent(event, predicate); } protected void callAppenders(final LogEvent event) { // 当前日志打印器的Appender会被封装成AppenderControl final AppenderControl[] controls appenders.get(); // 遍历当前日志打印器的Appender并执行相应的打印逻辑 for (int i 0; i controls.length; i) { controls[i].callAppender(event); } }通过上述源码可知LoggerConfig打印日志时会首先将本地日志打印包装成一个打印事件LogEvent然后使用当前日志打印器的过滤器如果有的话进行过滤再然后就会遍历当前日志打印器的Appender来打印日志最后在所有Appender打印完日志后还会判断当前日志打印器是否有父日志打印器如果有父日志打印器并且当前日志打印器的additivity属性为true则还会调用父日志打印器打印一次日志所以如果不想一条日志打印多次则最好在自己的自定义日志打印器中将additivity属性设置为false。下面继续跟进AppenderControl的callAppender()方法看一下一条LogEvent日志是如何被Appender打印的。public void callAppender(final LogEvent event) { // 在这里会使用Appender-ref级别的日志过滤器进行过滤 if (shouldSkip(event)) { return; } callAppenderPreventRecursion(event); } private void callAppenderPreventRecursion(final LogEvent event) { try { // recursive是一个ThreadLocal变量 // 这里将AppenderControl对象自身放到ThreadLocal中是用于判断是否有对Appender递归的调用 recursive.set(this); callAppender0(event); } finally { recursive.set(null); } } private void callAppender0(final LogEvent event) { ensureAppenderStarted(); // 在这里会使用Appender级别的日志过滤器进行过滤 if (!isFilteredByAppender(event)) { tryCallAppender(event); } } private void tryCallAppender(final LogEvent event) { try { // 让AppenderControl持有的Appender来进行日志打印 appender.append(event); } catch (final RuntimeException error) { handleAppenderError(event, error); } catch (final Throwable throwable) { handleAppenderError(event, new AppenderLoggingException(throwable)); } }由上可知AppenderControl会依次使用Appender-ref级别和Appender级别的过滤器进行过滤然后调用到实际的Appender来打印日志。在本文的示例中这里实际的Appender有ConsoleAppender进行标准日志打印和RollingFileAppender进行文件写入。下面简单的对ConsoleAppender和RollingFileAppender打印日志进行一个说明。首先是ConsoleAppender其将日志内容进行标准输出主要是依赖于其父类AbstractOutputStreamAppenderAbstractOutputStreamAppender的append()方法如下所示。public void append(final LogEvent event) { try { tryAppend(event); } catch (final AppenderLoggingException ex) { error(Unable to write to stream manager.getName() for appender getName(), event, ex); throw ex; } } private void tryAppend(final LogEvent event) { if (Constants.ENABLE_DIRECT_ENCODERS) { directEncodeEvent(event); } else { writeByteArrayToManager(event); } } protected void directEncodeEvent(final LogEvent event) { // 使用Appender的Layout对打印内容进行格式化然后转换为ByteBuffer并存放在manager中 // 这里的manager实际类型为OutputStreamManager getLayout().encode(event, manager); if (this.immediateFlush || event.isEndOfBatch()) { // 调用OutputStreamManager的flush()方法完成最终日志内容的标准输出 manager.flush(); } }ConsoleAppender对日志内容标准输出时首先会调用ConsoleAppender的Layout本示例中的Layout为PatternLayout来完成打印内容的格式化格式化后的打印内容会被转换为ByteBuffer并存放在OutputStreamManager中最后会调用OutputStreamManager的flush()方法完成日志内容的标准输出。现在再分析一下RollingFileAppender打印日志。RollingFileAppender的append()方法如下所示。public void append(final LogEvent event) { // 这里先拿到RollingFileManager来根据配置的各种触发策略来判断日志文件是否需要翻转 getManager().checkRollover(event); // 调用父类AbstractOutputStreamAppender的append()方法完成日志写入文件 super.append(event); }RollingFileAppender将日志内容写入文件时会先根据配置的各种翻转策略来判断当前是否已经达到翻转条件如果达到则翻转日志文件然后再调用到AbstractOutputStreamAppender的append()方法来完成日志内容的格式化以及写磁盘AbstractOutputStreamAppender的这部分逻辑与ConsoleAppender中的逻辑是一致的这里不再赘述。至此一条日志内容的打印流程分析完毕。这里进行一个小结即一条日志的打印会由一开始业务代码中的Logger依次传递给ReliabilityStrategyLoggerConfigAppender和Layout最后由Manager将日志进行刷盘打印。总结本篇文章主要是从使用Log4j2日志框架时日志打印器Logger的获取LoggerContext的获取以及实际一条日志的打印进行的讨论。对于日志打印器Logger的获取我们在应用程序中获取到的日志打印器实际就是org.apache.logging.slf4j.Log4jLogger但是实际打印日志是由其持有的LoggerConfig来完成而LoggerConfig是有一个父子层级的关系在里面也就是说name为com.lee.learn的日志打印器的LoggerConfig会优先取name为com.lee.learncom.lee和com的日志打印器的LoggerConfig如果都没有在配置文件中为这些日志打印器配置LoggerConfig则最后会将根日志打印器的LoggerConfig作为当前日志打印器的LoggerConfig。所以假如在log4j2.xml文件中的Loggers标签的内容如下。Loggers !-- 根日志打印器 -- Root levelINFO Appender-ref refMyConsole/ Appender-ref refMyFile/ /Root /Loggers那么相当于整个配置Configuration中只有根日志打印器的LoggerConfig那么所有日志打印器都将使用根日志打印器的LoggerConfig。同时每一个LoggerConfig打印完日志后还会判断其父LoggerConfig是否需要也打印这条日志判断的依据就是当前LoggerConfig的additive属性为true还是false这个属性值可以通过Logger additivityfalse来设置。LoggerContext实际维护了应用请求的所有Logger的列表和Log4j2的配置信息ConfigurationLoggerContext会在应用首次请求Logger时完成初始化。当在应用程序中使用Logger打印一条日志时这个打印的请求会由Logger的LoggerConfig来完成LoggerConfig中有一个appenders字段其是AppenderControl的集合而AppenderControl就是一个Appender的包装类所以LoggerConfig打印日志时会遍历AppenderControl的集合每个AppenderControl会使用其包装的Appender来打印日志当Appender打印一条日志时会先使用Appender的Layout来完成打印内容的格式化最后完成刷盘打印。最后本篇文章讨论的日志打印均为同步日志打印即应用线程和打印日志的线程为同一线程以一张流程示意图对Log4j2同步打印一条日志进行概括。