springboot的日志启动步骤主要分以下几步:
- Spring-jcl包,主要接口是
org.apache.commons.logging
包下面的LogFactory
类的getLog
方法,用来检测springframework的类路径下是否有Log4j 2.x
或者SLF4J 1.7
的接口,如果以上两个接口都没有实现,则会使用Commons Logging
接口。 - 第一步骤中根据接口的实现,选择
Log4j 2.x
或者SLF4J 1.7
,本文讨论SLF4J 1.7
接口,自动装配具体日志实现。 -
LogBack
实现
本文源码基于SpringBoot的2.4.3版本
一、spring-jcl
springboot启动入口类SpringApplication
有一个Logger静态变量
private static final Log logger = LogFactory.getLog(SpringApplication.class);
1.1 判定日志接口
[图片上传失败...(image-b4193b-1657185628706)]
LogAdapter #static{}
这里的LogFactory
和LogAdapter
都是srping-core包下面的spring-jcl包里面的类,其中LogAdapter
中配置了四个变量,除了LOG4J_SPI
是log4j
日志系统,其余的都是slf4j
日志系统。
继续往下看代码
isPresent()
方法就是一行Class.forName()方法,用来判定日志不同接口的具体实现类,然后用实现类创建日志。
1、 查找org.apache.logging.log4j.spi.ExtendedLogger
;
- 如果ExtendedLogger存在,那么继续查找
org.apache.logging.slf4j.SLF4JProvider
和org.slf4j.spi.LocationAwareLogger
,如果SLF4JProvider
和LocationAwareLogger
都存在,那么就启用SLF4J_LAL日志系统;如果SLF4JProvider
和LocationAwareLogger
有一个不存在,就启用LOG4J 2.X日志系统;
2、如果ExtendedLogger
不存在,就查找org.slf4j.spi.LocationAwareLogger
;
- 如果
LocationAwareLogger
存在,就启用SLF4J_LAL
日志系统; - 如果
LocationAwareLogger
不存在,就继续查找org.slf4j.Logger
;
3、如果org.slf4j.Logger
存在,就启用SLF4J
日志系统;
4、 如果以上都不存在,就启用JUL
日志系统。
1.2根据日志接口创建日志
--> LogAdapter.createLog(name)
至此,spring框架关于选择日志框架的代码已经结束了,第二部分会详细描写slf4j 的LoggerFactory是如何选择具体的日志实现框架。
1.3 加载spring-logback.xml
配置文件
spring-boot包下面的spring.factories
文件配置了如下配置,配置了日志监听器ApplicationListener的日志接口org.springframework.boot.context.logging.LoggingApplicationListener
1.3.1 LoggingApplicationListener
的监听事件如下:
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
- 获取loggingSystem:
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystemClassName = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystemClassName)) {
if (NONE.equals(loggingSystemClassName)) {
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystemClassName);
}
LoggingSystem loggingSystem = SYSTEM_FACTORY.getLoggingSystem(classLoader);
Assert.state(loggingSystem != null, "No suitable logging system located");
return loggingSystem;
}
其中,SYSTEM_FACTORY.getLoggingSystem(classLoader);
会从spring.factories中获取:
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
1.3.2 LoggingApplicationListener#onApplicationEnvironmentPreparedEvent()
初始化如下:
LoggingApplicationListener#onApplicationEnvironmentPreparedEvent()
---> #onApplicationEnvironmentPreparedEvent(env, AppClassLoader)
---> #initialize(ConfigurableEnvironment environment, ClassLoader classLoader)
---> #initializeSystem(ConfigurableEnvironment environment,LoggingSystem system, LogFile logFile)
---> org.springframework.boot.logging.logback.LogbackLoggingSystem# initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile)
-->
--->LogbackLoggingSystem#getStandardConfigLocations()
@Override
protected String[] getStandardConfigLocations() {
//自定义的日志配置文件加载顺序
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
"logback.xml" };
}
过程不赘述,,会在classpath下面查找以下的配置文件,并进行加载第一个找到的log配置文件,停止并重设loggerContext。
org.springframework.boot.logging.logback.LogbackLoggingSystem # loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile)
--> configureByResourceUrl()
---> ch.qos.logback.core.joran.GenericConfigurator# doConfigure
--> doConfigure(InputStream inputStream, String systemId)
--> doConfigure(final InputSource inputSource)
public final void doConfigure(final InputSource inputSource) throws JoranException {
long threshold = System.currentTimeMillis();
// if (!ConfigurationWatchListUtil.wasConfigurationWatchListReset(context)) {
// informContextOfURLUsedForConfiguration(getContext(), null);
// }
SaxEventRecorder recorder = new SaxEventRecorder(context);
recorder.recordEvents(inputSource); //将配置文件添加到saxEventList
doConfigure(recorder.saxEventList);
// no exceptions a this level
StatusUtil statusUtil = new StatusUtil(context);
if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
addInfo("Registering current configuration as safe fallback point");
registerSafeConfiguration(recorder.saxEventList);
}
}
ch.qos.logback.core.joran.event.SaxEventRecorder.#recordEvents
public List recordEvents(InputSource inputSource) throws JoranException {
SAXParser saxParser = buildSaxParser();
try {
saxParser.parse(inputSource, this);
return saxEventList;
} catch (IOException ie) {
handleError("I/O error occurred while parsing xml file", ie);
} catch (SAXException se) {
// Exception added into StatusManager via Sax error handling. No need to add it again
throw new JoranException("Problem parsing XML document. See previously reported errors.", se);
} catch (Exception ex) {
handleError("Unexpected exception while parsing XML document.", ex);
}
throw new IllegalStateException("This point can never be reached");
}
- -->
doConfigure(final List
eventList)
public void doConfigure(final List eventList) throws JoranException {
buildInterpreter();
// disallow simultaneous configurations of the same context
synchronized (context.getConfigurationLock()) {
interpreter.getEventPlayer().play(eventList);
}
}
第一步解析配置文件:buildInterpreter()
而buildInterpreter()
方法便是对处理读取的文件标签规则进行构建和初始化,其中有两个方法addInstanceRules和addImplicitRules便需要子类来具体实现某些规则。子类JoranConfigurator将会实现这两个方法,并且在ContextInitializer类中调用的类型也是JoranConfigurator。
具体读取解析XML文件的地方便是在SaxEventRecorder类中完成的,而对解析出来的SaxEvent对象完成Logback的解析读取则是在EventPlayer类中完成的,
protected void buildInterpreter() {
RuleStore rs = new SimpleRuleStore(context);
addInstanceRules(rs);
this.interpreter = new Interpreter(context, rs, initialElementPath());
InterpretationContext interpretationContext = interpreter.getInterpretationContext();
interpretationContext.setContext(context);
addImplicitRules(interpreter);
addDefaultNestedComponentRegistryRules(interpretationContext.getDefaultNestedComponentRegistry());
}
-
JoranConfiguratorBase.addInstanceRules
的源码
abstract public class JoranConfiguratorBase extends GenericConfigurator {
@Override
protected void addInstanceRules(RuleStore rs) {
// is "configuration/variable" referenced in the docs?
rs.addRule(new ElementSelector("configuration/variable"), new PropertyAction());
rs.addRule(new ElementSelector("configuration/property"), new PropertyAction());
rs.addRule(new ElementSelector("configuration/substitutionProperty"), new PropertyAction());
rs.addRule(new ElementSelector("configuration/timestamp"), new TimestampAction());
rs.addRule(new ElementSelector("configuration/shutdownHook"), new ShutdownHookAction());
rs.addRule(new ElementSelector("configuration/define"), new DefinePropertyAction());
// the contextProperty pattern is deprecated. It is undocumented
// and will be dropped in future versions of logback
rs.addRule(new ElementSelector("configuration/contextProperty"), new ContextPropertyAction());
rs.addRule(new ElementSelector("configuration/conversionRule"), new ConversionRuleAction());
rs.addRule(new ElementSelector("configuration/statusListener"), new StatusListenerAction());
rs.addRule(new ElementSelector("configuration/appender"), new AppenderAction());
rs.addRule(new ElementSelector("configuration/appender/appender-ref"), new AppenderRefAction());
rs.addRule(new ElementSelector("configuration/newRule"), new NewRuleAction());
rs.addRule(new ElementSelector("*/param"), new ParamAction(getBeanDescriptionCache()));
}
@Override
protected void addImplicitRules(Interpreter interpreter) {
// The following line adds the capability to parse nested components
NestedComplexPropertyIA nestedComplexPropertyIA = new NestedComplexPropertyIA(getBeanDescriptionCache());
nestedComplexPropertyIA.setContext(context);
interpreter.addImplicitAction(nestedComplexPropertyIA);
NestedBasicPropertyIA nestedBasicIA = new NestedBasicPropertyIA(getBeanDescriptionCache());
nestedBasicIA.setContext(context);
interpreter.addImplicitAction(nestedBasicIA);
}
@Override
protected void buildInterpreter() {
super.buildInterpreter();
Map omap = interpreter.getInterpretationContext().getObjectMap();
omap.put(ActionConst.APPENDER_BAG, new HashMap>());
//omap.put(ActionConst.FILTER_CHAIN_BAG, new HashMap());
}
public InterpretationContext getInterpretationContext() {
return interpreter.getInterpretationContext();
}
}
可以看到这个类的基本作用是i手动添加XML文件的读取规则,如
addDefaultNestedComponentRegistryRules
---> #addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegistry registry)
static public void addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegistry registry) {
registry.add(AppenderBase.class, "layout", PatternLayout.class);
registry.add(UnsynchronizedAppenderBase.class, "layout", PatternLayout.class);
registry.add(AppenderBase.class, "encoder", PatternLayoutEncoder.class);
registry.add(UnsynchronizedAppenderBase.class, "encoder", PatternLayoutEncoder.class);
registry.add(EvaluatorFilter.class, "evaluator", JaninoEventEvaluator.class);
SSLNestedComponentRegistryRules.addDefaultNestedComponentRegistryRules(registry);
}
第二步方法中完成对StartEvent、BodyEvent和EndEvent这三个标签的读取解析:
ch.qos.logback.core.joran.spi.EventPlayer#play(List
public void play(List aSaxEventList) {
eventList = aSaxEventList;
SaxEvent se;
for (currentIndex = 0; currentIndex < eventList.size(); currentIndex++) {
se = eventList.get(currentIndex);
if (se instanceof StartEvent) {
interpreter.startElement((StartEvent) se);
// invoke fireInPlay after startElement processing
interpreter.getInterpretationContext().fireInPlay(se);
}
if (se instanceof BodyEvent) {
// invoke fireInPlay before characters processing
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.characters((BodyEvent) se);
}
if (se instanceof EndEvent) {
// invoke fireInPlay before endElement processing
interpreter.getInterpretationContext().fireInPlay(se);
interpreter.endElement((EndEvent) se);
}
}
}
大致流程如下:
以startElement为例,有call对应的Action的操作,最后会调用Action接口的对应方法:
Action接口有很多实现,这里主要关注LoggerAction
这个类主要从LoggerContext获得logger为name的对象,并设置这个对象的level,因此我们才可以在Logback的日志配置文件里配置对某个包或某个类的单独日志级别。
二、SLF4J创建日志
org.slf4j.LoggerFactory#getLogger
- 1、-->
LoggerFactory#getILoggerFactory()
-->performInitialization()
-->bind()
-->findPossibleStaticLoggerBinderPathSet()
1.1StaticLoggerBinder.getSingleton();
init()
ContextInitializer(defaultLoggerContext).autoConfig()
- 2、
iLoggerFactory.getLogger
--->StaticLoggerBinder.getSingleton()
--->ContextSelectorStaticBinder.init()
SLF4J接口最关键的是两个接口:Logger
和ILoggerFactory
和一个入口类LoggerFactory
2.1 LoggerFactory
#getLogger()
方法:根据静态绑定返回一个ILoggerFactory的实例,然后委托这个实现类提供一个Looger实现类。这样讲把获取实际Logger的工作,委托给具体的日志框架上面。
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
getILoggerFactory()
->performInitialization()
->bind()
比较重要的bind()方法如下:首先判断是不是Android应用,检查是否有StaticLoggerBinder
类存在,判断这个类有没有getSingleton()
方法,
private final static void bind() {
try {
Set staticLoggerBinderPathSet = null;
if (!isAndroid()) {
//在路径下查找org/slf4j/impl/StaticLoggerBinder.class
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
//如果有多个绑定,则打印出来
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
} catch (NoClassDefFoundError ncde) {
} catch (java.lang.NoSuchMethodError nsme) {
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
} finally {
postBindCleanUp();
}
}
上图中的loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH)
就是查找符合类名是private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
的资源。
这个StaticLoggerBinder类,就是具体实现框架和slf4j框架对接的接口,除了logback,任何日志框架都是通过自己的StaticLoggerBinder
类和slf4j对接的。
这里有个疑问就是,引入的import org.slf4j.impl.StaticLoggerBinder;
类,slf4j包里面有没有这个类,那么框架是怎么编译通过并发布jar包的,在源码里面impl包里面有
但是在pom文件里面ignore了这几个文件,没有打进jar包
[图片上传失败...(image-1b2ee7-1657185628706)]
三、LogBack部分代码
由此就触发了logback
包下面的StaticLoggerBinder
来返回一个ILoggerFactory
3.1 StaticLoggerBinder.getSingleton()
获取LoggerFactory
StaticLoggerBinder
类实际继承了LoggerFactoryBinder
类,这个类主要有两个方法:
public interface LoggerFactoryBinder {
/**
* Return the instance of {@link ILoggerFactory} that
* {@link org.slf4j.LoggerFactory} class should bind to.
*
* @return the instance of {@link ILoggerFactory} that
* {@link org.slf4j.LoggerFactory} class should bind to.
*/
public ILoggerFactory getLoggerFactory();
/**
* The String form of the {@link ILoggerFactory} object that this
* LoggerFactoryBinder
instance is intended to return.
*
* This method allows the developer to interrogate this binder's intention
* which may be different from the {@link ILoggerFactory} instance it is able to
* yield in practice. The discrepancy should only occur in case of errors.
*
* @return the class name of the intended {@link ILoggerFactory} instance
*/
public String getLoggerFactoryClassStr();
}
下面来看StaticLoggerBinder
,#getSingleton()
这里是一个简单的单例模式,用init()
方法来初始化:
void init() {
try {
try {
//委托ContextInitializer类对defaultLoggerContext进行初始化
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
///对ContextSelectorStaticBinder类进行初始化
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Exception t) { // see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
这个方法主要做了两件事:①委托ContextInitializer类对defaultLoggerContext进行初始化:去找logback的配置文件,去初始化loggerContext;②对ContextSelectorStaticBinder类进行初始化:
public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
if (this.key == null) {
this.key = key;
} else if (this.key != key) {
throw new IllegalAccessException("Only certain classes can access this method.");
}
//获取系统配置logback.ContextSelector,判断是否配置成JNDI,来返回对应的selector,一般会返回DefaultContextSelector。
String contextSelectorStr = OptionHelper.getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR);
if (contextSelectorStr == null) {
contextSelector = new DefaultContextSelector(defaultLoggerContext);
} else if (contextSelectorStr.equals("JNDI")) {
// if jndi is specified, let's use the appropriate class
contextSelector = new ContextJNDISelector(defaultLoggerContext);
} else {
contextSelector = dynamicalContextSelector(defaultLoggerContext, contextSelectorStr);
}
}
至此一些初始化的动作就完成了,回到slf4j包的LoggerFactory#getILoggerFactory()
方法,调用了StaticLoggerBinder.getSingleton().getLoggerFactory();
:从下面源码可以看出,就是返回上面创建的defaultLoggerContext或者ContextSelectorStaticBinder返回一个ContextSelector(一般就是DefaultContextSelector),然后由ContextSelector来返回LoggerContext。
public ILoggerFactory getLoggerFactory() {
if (!initialized) {
return defaultLoggerContext;
}
if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
}
return contextSelectorBinder.getContextSelector().getLoggerContext();
}
3.2 LoggerContext
创建Logger
LoggerContext
内的字段
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
/** Default setting of packaging data in stack traces */
public static final boolean DEFAULT_PACKAGING_DATA = false;
//根rooter
final Logger root;
//loggerContext一共创建了几个logger
private int size;
private int noAppenderWarning = 0;
final private List loggerContextListenerList = new ArrayList();
//所有logger的缓存
private Map loggerCache;
//一个LoggerContext的VO对象,保存了LoggerContext的一些值,比如name、birthTime等
private LoggerContextVO loggerContextRemoteView;
//TurboFilter顾名思义,是一种快速过滤器,对是否记录日志有一票通过和一票否决的权力
private final TurboFilterList turboFilterList = new TurboFilterList();
private boolean packagingDataEnabled = DEFAULT_PACKAGING_DATA;
private int maxCallerDataDepth = ClassicConstants.DEFAULT_MAX_CALLEDER_DATA_DEPTH;
int resetCount = 0;
private List frameworkPackages;
public final Logger getLogger(final String name)
,根据类名获取Logger
@Override
public final Logger getLogger(final String name) {
……
int i = 0;
Logger logger = root;
// 先从缓存里面查询看Logger是否已经存在
Logger childLogger = (Logger) loggerCache.get(name);
if (childLogger != null) {
return childLogger;
}
// 如果不存在,则创建日志,如"org.springframework.boot.SpringApplication",会创建
//org, org.springframework, org.springframework.boot, org.springframework.boot.SpringApplication 四个Logger
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h + 1;
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
//微循环创建时,设置父节点
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}
总结:
- 如果请求ROOT,则直接返回root;
- 从loggerCache根据全限定名获取,如果可以获取,则直接返回;
- 如果从cache里面没有得到,则从根目录开始,级联创建所有的Logger,并且设置父子关系;
- 将创建好的Logger放入cache.
3.3 打印日志 ch.qos.logback.classic.Logger
首先Logger类实现了slf4j包的org.slf4j.Logger, LocationAwareLogger
两个接口,首先看看Logger的一些字段
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable, Serializable {
private static final long serialVersionUID = 5454405123156820674L; // 8745934908040027998L;
/**
* 类的全限定名
*/
public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();
/**
* The name of this logger
*/
private String name;
// The assigned levelInt of this logger. Can be null.
transient private Level level;
//类的有效level,如果上面level为空,则从父类继承
transient private int effectiveLevelInt;
/**
* 父Logger
*/
transient private Logger parent;
/**
* 子节点的集合
*/
transient private List childrenList;
/**
* It is assumed that once the 'aai' variable is set to a non-null value, it
* will never be reset to null. it is further assumed that only place where
* the 'aai'ariable is set is within the addAppender method. This method is
* synchronized on 'this' (Logger) protecting against simultaneous
* re-configuration of this logger (a very unlikely scenario).
*
*
* It is further assumed that the AppenderAttachableImpl is responsible for
* its internal synchronization and thread safety. Thus, we can get away with
* *not* synchronizing on the 'aai' (check null/ read) because
*
* 1) the 'aai' variable is immutable once set to non-null
*
* 2) 'aai' is getAndSet only within addAppender which is synchronized
*
* 3) all the other methods check whether 'aai' is null
*
* 4) AppenderAttachableImpl is thread safe
*/
transient private AppenderAttachableImpl aai;
/**
* Additivity is set to true by default, that is children inherit the
* appenders of their ancestors by default. If this variable is set to
* false
then the appenders located in the ancestors of this
* logger will not be used. However, the children of this logger will inherit
* its appenders, unless the children have their additivity flag set to
* false
too. See the user manual for more details.
*/
transient private boolean additive = true;
final transient LoggerContext loggerContext;
Logger的info()方法
#filterAndLog_0_Or3Plus()
-> buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t)
-> callAppenders(ILoggingEvent event)
-> appendLoopOnAppenders(ILoggingEvent event)
-
#callAppenders
方法就是从当前logger,一步一步向上遍历父类logger,如"org.springframework.boot.SpringApplication"
会一步步查找"org.springframework.boot"
、"org.springframework"
……"ROOT"
的logger
public void callAppenders(ILoggingEvent event) {
int writes = 0;
for (Logger l = this; l != null; l = l.parent) {
writes += l.appendLoopOnAppenders(event);
if (!l.additive) {
break;
}
}
// No appenders in hierarchy
if (writes == 0) {
loggerContext.noAppenderDefinedWarning(this);
}
}
-
appendLoopOnAppenders
实际上就是判断当前Logger的参数的AppenderAttachableImpl
是否为空,然后调用AppenderAttachableImpl.#appendLoopOnAppenders(E e)
,下面看下Appender的相关类:,主要是有个一Appender接口,UnsynchronizedAppenderBase类实现了这个接口,但是它本身是一个抽象类。
[图片上传失败...(image-39f949-1657185628706)]
首先看一下UnsynchronizedAppenderBase
类的doAppend()
方法
主要是记录了status状态,看filter,最后调用子类的appender()方法
public void doAppend(E eventObject) {
// WARNING: The guard check MUST be the first statement in the
// doAppend() method.
// prevent re-entry.
if (Boolean.TRUE.equals(guard.get())) {
return;
}
try {
guard.set(Boolean.TRUE);
if (!this.started) {
if (statusRepeatCount++ < ALLOWED_REPEATS) {
addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
}
return;
}
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}
// ok, we now invoke derived class' implementation of append
this.append(eventObject);
} catch (Exception e) {
if (exceptionCount++ < ALLOWED_REPEATS) {
addError("Appender [" + name + "] failed to append.", e);
}
} finally {
guard.set(Boolean.FALSE);
}
}
然后调用的是OutputStreamAppender
@Override
protected void append(E eventObject) {
if (!isStarted()) {
return;
}
subAppend(eventObject);
}
如果这个appender启动了,则继续调用subAppend()
方法:
protected void subAppend(E event) {
if (!isStarted()) {
return;
}
try {
if (event instanceof DeferredProcessingAware) {
((DeferredProcessingAware) event).prepareForDeferredProcessing();
}
// the synchronization prevents the OutputStream from being closed while we
// are writing. It also prevents multiple threads from entering the same
// converter. Converters assume that they are in a synchronized block.
// lock.lock();
byte[] byteArray = this.encoder.encode(event);
writeBytes(byteArray);
} catch (IOException ioe) {
// as soon as an exception occurs, move to non-started state
// and add a single ErrorStatus to the SM.
this.started = false;
addStatus(new ErrorStatus("IO failure in appender", this, ioe));
}
}
byte[] byteArray = this.encoder.encode(event);
将要打印的日志根据pattern组装成字符串,通过outputStream
将byyte写出到文件中。
如果是AsyncAppender
实际上会调用到AsyncAppenderBase
的append()
方法:
@Override
protected void append(E eventObject) {
if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) {
return;
}
preprocess(eventObject);
put(eventObject);
}
这个put()
方法实际上是将上面同步处理的EventObject放在队列里面:
private void put(E eventObject) {
if (neverBlock) {
blockingQueue.offer(eventObject);
} else {
putUninterruptibly(eventObject);
}
}
队列里面的数据由AsyncAppenderBase.Worker
类去处理:
class Worker extends Thread {
public void run() {
AsyncAppenderBase parent = AsyncAppenderBase.this;
AppenderAttachableImpl aai = parent.aai;
// loop while the parent is started
while (parent.isStarted()) {
try {
E e = parent.blockingQueue.take();
aai.appendLoopOnAppenders(e);
} catch (InterruptedException ie) {
break;
}
}
addInfo("Worker thread will flush remaining events before exiting. ");
for (E e : parent.blockingQueue) {
aai.appendLoopOnAppenders(e);
parent.blockingQueue.remove(e);
}
aai.detachAndStopAllAppenders();
}
最终调用的还是AppenderAttachableImpl#appendLoopOnAppenders()
方法。
参考:
1、https://blog.csdn.net/Peelarmy/article/details/106930569
2、https://www.cnblogs.com/lzghyh/p/14880309.html