美文网首页
Springboot + logback源码

Springboot + logback源码

作者: me0w | 来源:发表于2022-07-07 17:22 被阅读0次

    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

    相关文章

      网友评论

          本文标题:Springboot + logback源码

          本文链接:https://www.haomeiwen.com/subject/hroisrtx.html