Springboot + logback源码

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{}这里的LogFactoryLogAdapter都是srping-core包下面的spring-jcl包里面的类,其中LogAdapter中配置了四个变量,除了LOG4J_SPIlog4j日志系统,其余的都是slf4j日志系统。

image.png

继续往下看代码
LogAdapter 的static代码块

isPresent()方法就是一行Class.forName()方法,用来判定日志不同接口的具体实现类,然后用实现类创建日志。

1、 查找org.apache.logging.log4j.spi.ExtendedLogger

  • 如果ExtendedLogger存在,那么继续查找org.apache.logging.slf4j.SLF4JProviderorg.slf4j.spi.LocationAwareLogger,如果SLF4JProviderLocationAwareLogger都存在,那么就启用SLF4J_LAL日志系统;如果SLF4JProviderLocationAwareLogger有一个不存在,就启用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)

根据上文判定的创建日志

createLocationAwareLog

至此,spring框架关于选择日志框架的代码已经结束了,第二部分会详细描写slf4j 的LoggerFactory是如何选择具体的日志实现框架。

1.3 加载spring-logback.xml配置文件

spring-boot包下面的spring.factories文件配置了如下配置,配置了日志监听器ApplicationListener的日志接口org.springframework.boot.context.logging.LoggingApplicationListener

spring.factories
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<SaxEvent> 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<SaxEvent> eventList)
    public void doConfigure(final List<SaxEvent> 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<E> 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<E>());
        rs.addRule(new ElementSelector("configuration/appender/appender-ref"), new AppenderRefAction<E>());
        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<String, Object> omap = interpreter.getInterpretationContext().getObjectMap();
        omap.put(ActionConst.APPENDER_BAG, new HashMap<String, Appender<?>>());
        //omap.put(ActionConst.FILTER_CHAIN_BAG, new HashMap());
    }

    public InterpretationContext getInterpretationContext() {
        return interpreter.getInterpretationContext();
    }
}

可以看到这个类的基本作用是i手动添加XML文件的读取规则,如<appender/>和<logger/>标签里面的属性表情规则等。

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<SaxEvent> aSaxEventList)

public void play(List<SaxEvent> 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);
            }

        }
    }

大致流程如下:


20200623193038470.png
image.png

以startElement为例,有call对应的Action的操作,最后会调用Action接口的对应方法:

Action接口有很多实现,这里主要关注LoggerAction
image.png

这个类主要从LoggerContext获得logger为name的对象,并设置这个对象的level,因此我们才可以在Logback的日志配置文件里配置对某个包或某个类的单独日志级别。

二、SLF4J创建日志

org.slf4j.LoggerFactory#getLogger

  • 1、-->LoggerFactory#getILoggerFactory() -->performInitialization() -->bind()-->findPossibleStaticLoggerBinderPathSet()
    1.1 StaticLoggerBinder.getSingleton();
    init()
    ContextInitializer(defaultLoggerContext).autoConfig()
  • 2、iLoggerFactory.getLogger

--->StaticLoggerBinder.getSingleton()
--->ContextSelectorStaticBinder.init()

SLF4J接口最关键的是两个接口:LoggerILoggerFactory 和一个入口类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<URL> 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 
     * <code>LoggerFactoryBinder</code> instance is <em>intended</em> to return. 
     * 
     * <p>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<LoggerContextListener> loggerContextListenerList = new ArrayList<LoggerContextListener>();

    //所有logger的缓存
    private Map<String, Logger> 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<String> 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;
            }
        }
    }

总结:
  1. 如果请求ROOT,则直接返回root;
  2. 从loggerCache根据全限定名获取,如果可以获取,则直接返回;
  3. 如果从cache里面没有得到,则从根目录开始,级联创建所有的Logger,并且设置父子关系;
  4. 将创建好的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<ILoggingEvent>, 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<Logger> 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).
     * 
     * <p>
     * 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
     * <p>
     * 1) the 'aai' variable is immutable once set to non-null
     * <p>
     * 2) 'aai' is getAndSet only within addAppender which is synchronized
     * <p>
     * 3) all the other methods check whether 'aai' is null
     * <p>
     * 4) AppenderAttachableImpl is thread safe
     */
    transient private AppenderAttachableImpl<ILoggingEvent> 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
     * <code>false</code> 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
     * <code>false</code> 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

实际上会调用到AsyncAppenderBaseappend()方法:

    @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<E> parent = AsyncAppenderBase.this;
            AppenderAttachableImpl<E> 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容