(一)Logback-slf4j日志原理及源码启动分析

目录

一、Logback概述

1.Logback相较于Log4j的优势

2.日志架构

2.1 组成关系

2.2 类结构

二、启动源码分析

1.LoggerFactory

2.StaticLoggerBinder

3.ContextInitializer

4.Configurator

4.1 BasicConfigurator

4.2 GenericConfigurator

4.3 JoranConfigurator

5.SaxEventRecorder

6.EventPlayer

7.Interpreter

8.Action

9.LoggerContext


一、Logback概述

Logback是Log4j项目的继承者,使用过Log4j对Logback就能很快的上手,其配置方式和使用方式都差不多。Logback一共被分为三部分,logback-core,logback-classic和logback-access,core是Logback的基础核心部分,classic则是Logback对Log4j实现的升级版本,而access则实现了SLF4J接口,为项目切换logging、log4j或Java官方的日志JUL提供了无缝切换。

1.Logback相较于Log4j的优势

官方给出的优势点如下:

  • 更快的实现:重写编写了Logback的内核,相较于log4j在某些关键执行路径速度快了10倍;
  • 大量测试:在过去的几年用无数的时间进行了大量的测试,虽然log4j也进行了测试,但其测试级别远没有logback高;
  • logback-classic无缝实现SLF4J:classic实现了SLF4J接口,以至于你使用Logback时完全感受不到任何额外的操作;
  • 支持XML和Groovy:自从0.9.22版本开始便开始支持Groovy配置,此前只能使用XML;
  • 自动加载配置文件:在配置文件被修改时自动加载,扫描过程非常快,没有争用,并且动态地扩展到每秒数百万次调用,分布在数百个线程上;
  • 从IO失败中优雅的恢复:如果文件服务器暂时出了故障,不需要启动应用程序,当文件系统重新工作时,Logback将会透明迅速从以前的错误中恢复;
  • 自动删除旧的日志归档:使用TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory属性,到期后可自动删除日志归档;
  • 自动压缩日志文件:RollingFileAppender可自动的压缩日志归档文,并且日志压缩是异步的,因此压缩时不用担心程序会发生阻塞;
  • 谨慎模式:在多个JVM中运行的多个FileAppender实例可以安全的写入同一个日志文件,在某些限制条件下,RollingFileAppender也可以;
  • 配置文件的条件处理:Logback支持等条件标签,针对于不同的环境(如开发、测试和生产)使用同一个配置文件加上不同的条件限制即可完成各个环境的适配;
  • 过滤功能:如果生产上某个功能出了问题,而在测试环境却复现不出来,使用log4j就只能降低日志级别,但这样做将会导致非常多的日志被打印,不利于我们定位解决问题,使用过滤器则可以实现针对某一个用户来降低日志级别,从而使用某一个生产用户即可精确定位问题,并且不会导致程序性能受到影响;
  • SiftingAppender(筛选日志):可以根据任何运行时属性来分离或筛选日志。如根据不同的用户为每个用户生成一个只属于这个用户的操作日志;
  • 打包数据的堆栈信息:当程序抛出异常时将会打印包的版本和名字信息,让开发者知道这个这个错误堆栈信息发生在哪里;
  • Logback-access对Http及其它的日志功能的应用:日志访问模块(日志访问分发版的一部分)与Jetty或Tomcat等Servlet容器集成,以提供丰富而强大的http访问日志功能。

2.日志架构

2.1 组成关系

常用关键部分组件关系如下:

(一)Logback-slf4j日志原理及源码启动分析_第1张图片

看了这个图可以明显的将其分成四个部分:

  1. Appender及相关组件:完成具体日志打印或输出到文件的某些特定功能,可以理解成具体实现Logback多样性功能的地方;
  2. Status相关:在Logback运行过程时将一些关键状态保存起来进行管理;
  3. LoggerFactory相关:用来生成和管理Logger对象;
  4. Context:Logback的上下文,和Spring框架中的ApplicationContext类似,用来统一管理Logback日志框架的各个功能组件。

2.2 类结构

主要类组成:

(一)Logback-slf4j日志原理及源码启动分析_第2张图片

乍一看确实是感觉很杂很乱,但只要沿着实现关系分析,就能够看出来这些类组成基本上是和上那一张图所说的四个部分是一致的,基本上就是那四个部分,只是实现更加细化:

  1. Appender相关:在编写日志配置文件时,可以知道Appender标签都是和Layout、Encoder和RollingPolicy等标签互相搭配使用的,在这个类图中就可以看到他们的关系,Layout和Encoder接口是Appender的实现或组成部分,而其它的Policy接口则是提供更加丰富多样的功能,如根据某些条件执行某些动作等等;
  2. Status相关:可以看到在Logback中Status具体分为了ErrorStatus、WarnStatue和InfoStatus三个具体实现类,并且都会在ContextInitializer类中被StatusManger管理起来并存放进Context中以便后续使用;
  3. LoggerFactory相关:Logger利用ILoggingEvent获得LoggingEvent,接着使用Event将其一一转换成Logger,最后通过ILoggerFactory获取,并将前面生成的Appender引入到Logger中,实现自定义功能;
  4. Context相关:以StaticLoggerBinder类为中间类,调用ContextInitializer初始化Logback的上下文,并将Status和Logger等信息一并放进Context中。

二、启动源码分析

1.LoggerFactory

按照老规矩,我们先从唯一能接触到的入口开始分析,即LoggerFactory.getLogger()方法,其部分源码如下:

public final class LoggerFactory {
    static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
    static final String NO_STATICLOGGERBINDER_URL = 
            CODES_PREFIX + "#StaticLoggerBinder";
    static final String MULTIPLE_BINDINGS_URL = 
            CODES_PREFIX + "#multiple_bindings";
    static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
    static final String VERSION_MISMATCH = 
            CODES_PREFIX + "#version_mismatch";
    static final String SUBSTITUTE_LOGGER_URL = 
            CODES_PREFIX + "#substituteLogger";
    static final String LOGGER_NAME_MISMATCH_URL = 
            CODES_PREFIX + "#loggerNameMismatch";
    static final String REPLAY_URL = CODES_PREFIX + "#replay";
    static final String UNSUCCESSFUL_INIT_URL = 
            CODES_PREFIX + "#unsuccessfulInit";
    static final String UNSUCCESSFUL_INIT_MSG = 
            "org.slf4j.LoggerFactory in failed state. Original " +
            + "exception was thrown EARLIER. See also " + 
            UNSUCCESSFUL_INIT_URL;
            
    static final int UNINITIALIZED = 0;
    static final int ONGOING_INITIALIZATION = 1;
    static final int FAILED_INITIALIZATION = 2;
    static final int SUCCESSFUL_INITIALIZATION = 3;
    static final int NOP_FALLBACK_INITIALIZATION = 4;
    
    static volatile int INITIALIZATION_STATE = UNINITIALIZED;
    static final SubstituteLoggerFactory SUBST_FACTORY = 
            new SubstituteLoggerFactory();
    static final NOPLoggerFactory NOP_FALLBACK_FACTORY = 
            new NOPLoggerFactory();
    static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = 
            "slf4j.detectLoggerNameMismatch";
    static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
    static boolean DETECT_LOGGER_NAME_MISMATCH = 
            Util.safeGetBooleanSystemProperty(
                    DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
                    
    static private final String[] API_COMPATIBILITY_LIST = 
            new String[] { "1.6", "1.7" };
            
    public static Logger getLogger(Class clazz) {
        Logger logger = getLogger(clazz.getName());
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && 
                    nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report("", logger.getName(),
                                autoComputedCallingClass.getName()));
                Util.report();
            }
        }
        return logger;
    }
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
}

LoggerFactory这个类中只有两个getLogger方法,一个参数是类,一个参数是字符串,当参数是类时最终还是会使用类的名字去调用参数是字符串的方法。最终方法会调用进getIloggerFactory中,在这里面会完成Logback的初始化。

getIloggerFactory方法部分关键源码如下:

public final class LoggerFactory {
    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }
    private final static void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }
}

在getILoggerFactory方法中将会首先判断Logback是否已经初始化成功,如果没有初始化则会调用performInitialization方法进行文件属性的绑定以及版本检查。如果已经绑定成功则调用到在类图那里所说的StaticLoggerBinder中间类,进而获取LoggerFacotry。

这里需要注意的是判断是否已经实例化那里,有两层if判断,第一层判断是初次筛选,而同步块里面的if则是判断进入同步块的线程,以避免重复初始化。

绑定方法部分源码如下:

public final class LoggerFactory {
    private final static void bind() {
        try {
            Set staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                staticLoggerBinderPathSet = 
                        findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                ...
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && 
                    msg.contains("...")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                ...
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("...", e);
        }
    }
}

这个方法最重要的是StaticLoggerBinder.getSingleton()语句,其它的都是进行一些前置或后置条件。如findPossibleStaticLoggerBinderPathSet方法这一块是为了在类路径中获得StaticLoggerBinder类,而fixSubstituteLoggers等方法则是为了修正SUBST_FACTORY或其它属性。截止到这,LoggerFactory这个入口类的大致作用便完成了。

2.StaticLoggerBinder

其入口关键部分代码如下:

public class StaticLoggerBinder implements LoggerFactoryBinder {
    public static String REQUESTED_API_VERSION = "1.7.16";
    final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS";
    
    private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    private static Object KEY = new Object();
    static {
        SINGLETON.init();
    }
    
    private boolean initialized = false;
    private LoggerContext defaultLoggerContext = new LoggerContext();
    private final ContextSelectorStaticBinder contextSelectorBinder = 
            ContextSelectorStaticBinder.getSingleton();
    private StaticLoggerBinder() {
        defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
    }
    public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }
    static void reset() {
        SINGLETON = new StaticLoggerBinder();
        SINGLETON.init();
    }
    public ILoggerFactory getLoggerFactory() {
        if (!initialized) {
            return defaultLoggerContext;
        }
        if (contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("..." + NULL_CS_URL);
        }
        return contextSelectorBinder.getContextSelector()
                .getLoggerContext();
    }
}

首先将其getSingleton方法相关的关键成员属性以及方法都看一下,可以看到StaticLoggerBinder 是一个单例对象,其内部有一个默认的LoggerContext对象,默认名字参数便是default,并且还有一个静态代码快,里面直接调用了单例对象的init初始化方法,所以等下我们直接看到init方法中。还有一个方法便是LoggerFactoryBinder接口的getLoggerFactory方法,当这个类初始化后调用这个方法便可以获得ILoggerFactory,从方法返回结果来看,返回的便是LoggerContext。

init方法部分关键代码:

public class StaticLoggerBinder implements LoggerFactoryBinder {
    void init() {
        try {
            try {
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                ...
            }
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)){
                StatusPrinter.printInCaseOfErrorsOrWarnings(
                        defaultLoggerContext);
            }
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            initialized = true;
        } catch (Exception t) { 
            ...
        }
    }
}

可以看到init方法是非常简单的,其主要分两部分,一个部分便是使用ContextInitializer类来具体初始化LoggerContext,第二部分便是对初始化结果进行验证和赋值,如果初始化失败了则调用printInCaseOfErrorsOrWarnings方法来打印失败信息,若成功则将LoggerContext赋值进ContextSelectorStaticBinder对象中,以便getLoggerFactory方法获得调用。同时将initialized标识设置为true,意味着初始化成功。

3.ContextInitializer

该类的部分关键代码如下:

public class ContextInitializer {
    final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
    final public static String AUTOCONFIG_FILE = "logback.xml";
    final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
    final public static String CONFIG_FILE_PROPERTY = 
            "logback.configurationFile";
    final LoggerContext loggerContext;
    public ContextInitializer(LoggerContext loggerContext) {
        this.loggerContext = loggerContext;
    }
    public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
        ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
        URL url = 
                findConfigFileURLFromSystemProperties(myClassLoader, 
                        updateStatus);
        if (url != null) {
            return url;
        }
        url = 
            getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }
        url = 
            getResource(GROOVY_AUTOCONFIG_FILE,myClassLoader,updateStatus);
        if (url != null) {
            return url;
        }
        return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
    }
}

看到这个类的成员属性值便可以大致猜到,前面的logback.xml、logback-test.xml等值便是logback默认读取的文件名和地址,而logback.configurationFile就是从系统属性中获取的key。而findURLOfDefaultConfigurationFile方法则将其具体调用的顺序写死在代码中。顺序分别为:logback.configurationFile配置值->logback-test.xml->logback.groovy->logback.xml。

接下来接着上面的调用方法链看到autoConfig方法源码:

public class ContextInitializer {
    public void autoConfig() throws JoranException {
        StatusListenerConfigHelper.installIfAsked(loggerContext);
        URL url = findURLOfDefaultConfigurationFile(true);
        if (url != null) {
            configureByResource(url);
        } else {
            Configurator c = 
                    EnvUtil.loadFromServiceLoader(Configurator.class);
            if (c != null) {
                try {
                    c.setContext(loggerContext);
                    c.configure(loggerContext);
                } catch (Exception e) {
                    throw new LogbackException("..."), e);
                }
            } else {
                BasicConfigurator basicConfigurator = 
                        new BasicConfigurator();
                basicConfigurator.setContext(loggerContext);
                basicConfigurator.configure(loggerContext);
            }
        }
    }
    public void configureByResource(URL url) throws JoranException {
        if (url == null) {
            throw new IllegalArgumentException("...");
        }
        final String urlString = url.toString();
        if (urlString.endsWith("groovy")) {
            if (EnvUtil.isGroovyAvailable()) {
                GafferUtil
                        .runGafferConfiguratorOn(loggerContext, this, url);
            } else {
                StatusManager sm = loggerContext.getStatusManager();
                sm.add(new ErrorStatus("...", loggerContext));
            }
        } else if (urlString.endsWith("xml")) {
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(loggerContext);
            configurator.doConfigure(url);
        } else {
            throw new LogbackException("...");
        }
    }
}

可以看到autoConfig方法中的逻辑很简单,判断findURLOfDefaultConfigurationFile方法是否返回了可以读取的URL,如果返回了则调用configureByResource方法,根据URL去读取文件进行配置,而如果URL返回为null,则直接使用Configurator及其实现子类的configure方法对LoggerContext对象进行配置。

configureByResource方法逻辑也很简单,直接判断文件后缀是XML还是Groovy,Groovy配置我们不分析,因此只看到解析XML文件的,最终会调用进JoranConfigurator对象的doConfigure配置方法中。

4.Configurator

经过上面的ContextInitializer类方法流程,我们确定了在ContextInitializer类中最终不管如何还是会调用进Configurator这一系列实现类中,因此我们看到Configurator接口以及其主要实现子类中来:

public interface Configurator extends ContextAware {
    public void configure(LoggerContext loggerContext);
}

可以看到Configurator接口就一个方法configure,在这里面进行上下文配置。

4.1 BasicConfigurator

Configurator的方法configure有一个唯一实现子类BasicConfigurator,其源码如下:

public class BasicConfigurator extends ContextAwareBase 
        implements Configurator {
    public BasicConfigurator() {
    }
    public void configure(LoggerContext lc) {
        addInfo("Setting up default configuration.");
        
        ConsoleAppender ca = 
                new ConsoleAppender();
        ca.setContext(lc);
        ca.setName("console");
        LayoutWrappingEncoder encoder = 
                new LayoutWrappingEncoder();
        encoder.setContext(lc);
        
        TTLLLayout layout = new TTLLLayout();
 
        layout.setContext(lc);
        layout.start();
        encoder.setLayout(layout);
        
        ca.setEncoder(encoder);
        ca.start();
        
        Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
        rootLogger.addAppender(ca);
    }
}

可以看到这个方法非常简单,便是创建默认的ROOT节点,并为其设置Appender,Layout等属性,这个方法在ContextInitializer类中如果找不到配置文件将会被调用,因此可以看成是一个保底的实现类方法。至于日志生命周期start等方法便在后续篇幅展开。

4.2 GenericConfigurator

JoranConfigurator是GenericConfigurator的子类,因此先看到GenericConfigurator部分源码:

public abstract class GenericConfigurator extends ContextAwareBase {
    private BeanDescriptionCache beanDescriptionCache;
    protected Interpreter interpreter;
    public final void doConfigure(URL url) throws JoranException {
        InputStream in = null;
        try {
            informContextOfURLUsedForConfiguration(getContext(), url);
            URLConnection urlConnection = url.openConnection();
            urlConnection.setUseCaches(false);
    
            in = urlConnection.getInputStream();
            doConfigure(in, url.toExternalForm());
        } catch (IOException ioe) {
            String errMsg = "Could not open URL [" + url + "].";
            addError(errMsg, ioe);
            throw new JoranException(errMsg, ioe);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ioe) {
                    String errMsg = "Could not close input stream";
                    addError(errMsg, ioe);
                    throw new JoranException(errMsg, ioe);
                }
            }
        }
    }
    public final void doConfigure(InputStream inputStream, 
            String systemId) throws JoranException {
        InputSource inputSource = new InputSource(inputStream);
        inputSource.setSystemId(systemId);
        doConfigure(inputSource);
    }
    public final void doConfigure(final InputSource inputSource)
            throws JoranException {
        long threshold = System.currentTimeMillis();
        SaxEventRecorder recorder = new SaxEventRecorder(context);
        recorder.recordEvents(inputSource);
        doConfigure(recorder.saxEventList);
        StatusUtil statusUtil = new StatusUtil(context);
        if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
            addInfo("...");
            registerSafeConfiguration(recorder.saxEventList);
        }
    }
    public void doConfigure(final List eventList)
            throws JoranException {
        buildInterpreter();
        synchronized (context.getConfigurationLock()) {
            interpreter.getEventPlayer().play(eventList);
        }
    }
    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());
    }
}

当在ContextInitializer类中获取到了默认的XML文件,将会调用到这里来。虽然这个方法流程共有四个方法,但这四个方法无非是对文件进行另一层的封装以及验证,具体封装顺序为URL->InputStream->inputSource->List。最后的List类型方法将会被子类重写。

而buildInterpreter方法便是对处理读取的文件标签规则进行构建和初始化,其中有两个方法addInstanceRules和addImplicitRules便需要子类来具体实现某些规则。子类JoranConfigurator将会实现这两个方法,并且在ContextInitializer类中调用的类型也是JoranConfigurator。

具体读取解析XML文件的地方便是在SaxEventRecorder类中完成的,而对解析出来的SaxEvent对象完成Logback的解析读取则是在EventPlayer类中完成的,这些后面再分析。

4.3 JoranConfigurator

接下来看到JoranConfigurator类的源码:

abstract public class JoranConfiguratorBase 
        extends GenericConfigurator {
    @Override
    protected void addInstanceRules(RuleStore rs) {
        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());

        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) {
        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>());
    }
    public InterpretationContext getInterpretationContext() {
        return interpreter.getInterpretationContext();
    }
}
public class JoranConfigurator 
        extends JoranConfiguratorBase {
    @Override
    public void addInstanceRules(RuleStore rs) {
        super.addInstanceRules(rs);

        rs.addRule(new ElementSelector("configuration"), 
                new ConfigurationAction());
        rs.addRule(new ElementSelector("configuration/contextName"), 
                new ContextNameAction());
        rs.addRule(new ElementSelector("configuration/contextListener"), 
                new LoggerContextListenerAction());
        rs.addRule(new ElementSelector("configuration/insertFromJNDI"), 
                new InsertFromJNDIAction());
        rs.addRule(new ElementSelector("configuration/evaluator"), 
                new EvaluatorAction());

        rs.addRule(new ElementSelector("configuration/appender/sift"), 
                new SiftAction());
        rs.addRule(new ElementSelector("configuration/appender/sift/*"), 
                new NOPAction());
        rs.addRule(new ElementSelector("configuration/logger"), 
                new LoggerAction());
        rs.addRule(new ElementSelector("configuration/logger/level"), 
                new LevelAction());
        rs.addRule(new ElementSelector("configuration/root"), 
                new RootLoggerAction());
        rs.addRule(new ElementSelector("configuration/root/level"), 
                new LevelAction());
        rs.addRule(
                new ElementSelector("configuration/logger/appender-ref"), 
                        new AppenderRefAction());
        rs.addRule(new ElementSelector("configuration/root/appender-ref"),
                new AppenderRefAction());

        rs.addRule(new ElementSelector("*/if"), new IfAction());
        rs.addRule(new ElementSelector("*/if/then"), new ThenAction());
        rs.addRule(new ElementSelector("*/if/then/*"), new NOPAction());
        rs.addRule(new ElementSelector("*/if/else"), new ElseAction());
        rs.addRule(new ElementSelector("*/if/else/*"), new NOPAction());

        if (PlatformInfo.hasJMXObjectName()) {
            rs.addRule(
                    new ElementSelector("configuration/jmxConfigurator"), 
                    new JMXConfiguratorAction());
        }
        rs.addRule(new ElementSelector("configuration/include"), 
                new IncludeAction());
        rs.addRule(new ElementSelector("configuration/consolePlugin"), 
                new ConsolePluginAction());
        rs.addRule(new ElementSelector("configuration/receiver"), 
                new ReceiverAction());
    }
    @Override
    protected void 
            addDefaultNestedComponentRegistryRules(
                    DefaultNestedComponentRegistry registry) {
        DefaultNestedComponentRules
                .addDefaultNestedComponentRegistryRules(registry);
    }
}

可以看到JoranConfigurator和其父类JoranConfiguratorBase基本的作用便是手动添加XML文件读取规则,诸如标签和里面的属性标签规则等,还有Logback特殊的if else等标签规则,可以说Logback的配置规则在这两个类里面便能够看出来大致有哪些了。在这个类中添加完读取XML文件的规则后便会在父类GenericConfigurator的doConfigure方法中完成规则识别和读取。

5.SaxEventRecorder

我们从前面分析GenericConfigurator类的一系列doConfigure方法时,在针对InputSource参数进行解析时实例化了这个对象,并且把Context对象作为参数传了进去,现在我们来看看这里面具体做了什么。部分源码如下:

public class SaxEventRecorder extends DefaultHandler 
        implements ContextAware {
    final ContextAwareImpl cai;
    public SaxEventRecorder(Context context) {
        cai = new ContextAwareImpl(context, this);
    }
    public List saxEventList = new ArrayList();
    Locator locator;
    ElementPath globalElementPath = new ElementPath();
    final public void recordEvents(InputStream inputStream) 
            throws JoranException {
        recordEvents(new InputSource(inputStream));
    }
    public List recordEvents(InputSource inputSource) 
            throws JoranException {
        SAXParser saxParser = buildSaxParser();
        try {
            saxParser.parse(inputSource, this);
            return saxEventList;
        } catch (IOException ie) {
            handleError("...", ie);
        } catch (SAXException se) {
            throw new JoranException("...", se);
        } catch (Exception ex) {
            handleError("...", ex);
        }
        throw new IllegalStateException("...");
    }
    private SAXParser buildSaxParser() throws JoranException {
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setValidating(false);
            spf.setNamespaceAware(true);
            return spf.newSAXParser();
        } catch (Exception pce) {
            String errMsg = "Parser configuration error occurred";
            addError(errMsg, pce);
            throw new JoranException(errMsg, pce);
        }
    }
    public void startElement(String namespaceURI, String localName, 
            String qName, Attributes atts) {
        String tagName = getTagName(localName, qName);
        globalElementPath.push(tagName);
        ElementPath current = globalElementPath.duplicate();
        saxEventList.add(new StartEvent(current, namespaceURI, localName, 
                qName, atts, getLocator()));
    }
    public void characters(char[] ch, int start, int length) {
        String bodyStr = new String(ch, start, length);
        SaxEvent lastEvent = getLastEvent();
        if (lastEvent instanceof BodyEvent) {
            BodyEvent be = (BodyEvent) lastEvent;
            be.append(bodyStr);
        } else {
            if (!isSpaceOnly(bodyStr)) {
                saxEventList.add(new BodyEvent(bodyStr, getLocator()));
            }
        }
    }
    public void endElement(String namespaceURI, String localName, 
            String qName) {
        saxEventList.add(new EndEvent(namespaceURI, localName, qName, 
                getLocator()));
        globalElementPath.pop();
    }
}

本次分析只取了一部分方法,就是在doConfigure一系列调用方法链中调用到的recordEvents方法,在这个方法中我们可以看到构建了一个SAXParser对象,这个对象看过Spring解析Spring的xml就能知道,这是Java官方提供给开发者的读取XML文件工具,只需要调用即可。

但是需要注意的是SAXParser类的parse方法除了InputSource参数还有一个DefaultHandler参数,DefaultHandler参数便是将XML标签内容转变成自己框架内能使用读取的关键。在Logback中对DefaultHandler最重要的实现有三个方法:startElement、characters和endElement。这三个方法分别对应标签头、标签体和标签尾,实际上标签在XML中也会被读取为三个标签。在这三个方法中将解析获得的标签属性和内容使用Logback的SaxEvent实现类封装并添加进saxEventList中。而后续读取解析就只需要获得saxEventList即可。

6.EventPlayer

在上面获得了saxEventList之后,就需要对这些标签内容进行解析,而Logback解析这些标签属性的地方便是在EventPlayer类中。在GenericConfigurator类中调用的便是这个类的play方法。其部分关键源码如下:

public class EventPlayer {
    final Interpreter interpreter;
    List eventList;
    int currentIndex;
    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);
                interpreter.getInterpretationContext().fireInPlay(se);
            }
            if (se instanceof BodyEvent) {
                interpreter.getInterpretationContext().fireInPlay(se);
                interpreter.characters((BodyEvent) se);
            }
            if (se instanceof EndEvent) {
                interpreter.getInterpretationContext().fireInPlay(se);
                interpreter.endElement((EndEvent) se);
            }
    
        }
    }
    public void addEventsDynamically(List eventList, int offset){
        this.eventList.addAll(currentIndex + offset, eventList);
    }
}

简单明了,只涉及了这个类中的play方法,在play方法中完成对StartEvent、BodyEvent和EndEvent这三个标签的读取解析,具体的解析方法在Interpreter类中,等下我们再去分析。除了play方法外还有另外一个方法addEventsDynamically,这个方法的作用便是供外部调用添加SaxEvent到这个类当中,以便于后续调用play方法完成解析。

7.Interpreter

这个类实际上在GenericConfigurator类中就已经被调用过了,如在其中添加各种ruler便是在其实现子类JoranConfigurator中完成的。Interpreter的部分关键源码如下:

public class Interpreter {
    private static List EMPTY_LIST = new Vector(0);

    final private RuleStore ruleStore;
    final private InterpretationContext interpretationContext;
    final private ArrayList implicitActions;
    final private CAI_WithLocatorSupport cai;
    private ElementPath elementPath;
    Locator locator;
    EventPlayer eventPlayer;
    Stack> actionListStack;
    ElementPath skip = null;
    public void startElement(StartEvent se) {
        // EventPlayer首先会调用这个方法,从se中获得具体的属性
        setDocumentLocator(se.getLocator());
        startElement(se.namespaceURI, se.localName, se.qName, 
                se.attributes);
    }
    private void startElement(String namespaceURI, String localName, 
            String qName, Attributes atts) {
        // 获得标签的具体名字,如果localName有则取localName
        // 一般而言localName和qName是一致的
        String tagName = getTagName(localName, qName);
        // 将标签名称放进elementPath中,elementPath里面封装了一个ArrayList
        // 这个列表将会记录当前的标签路径
        elementPath.push(tagName);
        if (skip != null) {
            pushEmptyActionList();
            return;
        }
        // 判断刚刚获得的标签路径名称是否符合规则的匹配机制
        List applicableActionList = 
                getApplicableActionList(elementPath, atts);
        // 如果匹配则对标签进行下一步操作
        if (applicableActionList != null) {
            actionListStack.add(applicableActionList);
            callBeginAction(applicableActionList, tagName, atts);
        } else {
            // 如果标签匹配失败则添加空的Action列表进去
            pushEmptyActionList();
            String errMsg = "...";
            cai.addError(errMsg);
        }
    }
    public void characters(BodyEvent be) {
        setDocumentLocator(be.locator);
        // 获取标签内容
        String body = be.getText();
        // 使用peek,只获取栈顶元素,而不删除
        List applicableActionList = actionListStack.peek();
        if (body != null) {
            body = body.trim();
            if (body.length() > 0) {
                // 如果有内容则调用处理body的方法
                callBodyAction(applicableActionList, body);
            }
        }
    }
    public void endElement(EndEvent endEvent) {
        setDocumentLocator(endEvent.locator);
        endElement(endEvent.namespaceURI, endEvent.localName, 
                endEvent.qName);
    }
    private void endElement(String namespaceURI, String localName, 
            String qName) {
        // 从actionListStack中出栈
        List applicableActionList = 
                (List) actionListStack.pop();
        if (skip != null) {
            if (skip.equals(elementPath)) {
                skip = null;
            }
        } else if (applicableActionList != EMPTY_LIST) {
            // 如果Action列表不为空,则对标签进行处理
            callEndAction(applicableActionList, 
                    getTagName(localName, qName));
        }
        // 将元素路径出栈(删除ArrayList最后一个元素)
        elementPath.pop();
    }
    void callBeginAction(List applicableActionList, String tagName,
            Attributes atts) {
        if (applicableActionList == null) {
            return;
        }
        // 从Action列表中获取元素
        Iterator i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = (Action) i.next();
            try {
                // 调用不同Action实现子类的begin方法
                action.begin(interpretationContext, tagName, atts);
            } catch (ActionException e) {
                skip = elementPath.duplicate();
                cai.addError("...", e);
            } catch (RuntimeException e) {
                skip = elementPath.duplicate();
                cai.addError("...", e);
            }
        }
    }
    private void callBodyAction(List applicableActionList, 
            String body) {
        if (applicableActionList == null) {
            return;
        }
        Iterator i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = i.next();
            try {
                // 调用不同Action子类的body方法
                action.body(interpretationContext, body);
            } catch (ActionException ae) {
                cai.addError("...", ae);
            }
        }
    }
    private void callEndAction(List applicableActionList, 
            String tagName) {
        if (applicableActionList == null) {
            return;
        }
        Iterator i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = i.next();
            try {
                // 调用不同Action子类的end方法
                action.end(interpretationContext, tagName);
            } catch (ActionException ae) {
                cai.addError("...", ae);
            } catch (RuntimeException e) {
                cai.addError("...", e);
            }
        }
    }
}

这个流程有点复杂,因此对于具体的流程便写在了代码中。

其大致图示流程如下图,关于图片的描述见图片流程即可:

(一)Logback-slf4j日志原理及源码启动分析_第3张图片

8.Action

上面已经可以看到对于不同的标签会有对应的Action,因此我们看到主要看到LoggerAction,看看Logger是如何被添加进LoggerContext对象的。LoggerAction部分关键源码如下:

public class LoggerAction extends Action {
    public static final String LEVEL_ATTRIBUTE = "level";
    boolean inError = false;
    Logger logger;
    public void begin(InterpretationContext ec, String name, 
            Attributes attributes) {
        inError = false;
        logger = null;
        LoggerContext loggerContext = (LoggerContext) this.context;
    
        String loggerName = ec.subst(attributes.getValue(NAME_ATTRIBUTE));
        if (OptionHelper.isEmpty(loggerName)) {
            inError = true;
            String aroundLine = getLineColStr(ec);
            String errorMsg = "..." + aroundLine;
            addError(errorMsg);
            return;
        }
        logger = loggerContext.getLogger(loggerName);
        String levelStr = ec.subst(attributes.getValue(LEVEL_ATTRIBUTE));
    
        if (!OptionHelper.isEmpty(levelStr)) {
            if (ActionConst.INHERITED.equalsIgnoreCase(levelStr) || 
                    ActionConst.NULL.equalsIgnoreCase(levelStr)) {
                addInfo("...");
                logger.setLevel(null);
            } else {
                Level level = Level.toLevel(levelStr);
                addInfo("..." + level);
                logger.setLevel(level);
            }
        }
    
        String additivityStr = ec.subst(attributes
                .getValue(ActionConst.ADDITIVITY_ATTRIBUTE));
        if (!OptionHelper.isEmpty(additivityStr)) {
            boolean additive = OptionHelper.toBoolean(additivityStr, true);
            addInfo("..." + additive);
            logger.setAdditive(additive);
        }
        ec.pushObject(logger);
    }
}

可以看到流程很简单,从LoggerContext获得logger为name的对象,并设置这个对象的level,因此我们才可以在Logback的日志配置文件里配置对某个包或某个类的单独日志级别。但是这个方法依旧没解决Logger从哪里来的问题,我们在这里面只看到了loggerContext.getLogger()方法直接获得了logger,因此可以暂时推断Logger就是在这里面生成的,只是级别是在LoggerAction设置的。

9.LoggerContext

终于分析到Logback的上下文类了,在这个类里将会解决Logger的生成。其部分关键源码如下:

public class LoggerContext extends ContextBase 
        implements ILoggerFactory, LifeCycle {
    final Logger root;
    private int size;
    private Map loggerCache;
    public LoggerContext() {
        super();
        this.loggerCache = new ConcurrentHashMap();
        this.loggerContextRemoteView = new LoggerContextVO(this);
        this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
        this.root.setLevel(Level.DEBUG);
        loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
        initEvaluatorMap();
        size = 1;
        this.frameworkPackages = new ArrayList();
    }
    public final Logger getLogger(final Class clazz) {
        return getLogger(clazz.getName());
    }
    @Override
    public final Logger getLogger(final String name) {
        // 每个logger一定会有名字,因此会判断
        if (name == null) {
            throw new IllegalArgumentException("...");
        }
        // 因为root根节点是一定会存在的,不会存在第二个,因此名字是root直接返回
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
            return root;
        }
        int i = 0;
        Logger logger = root;
        // 从logger缓存中获取logger(如果有的话)
        Logger childLogger = (Logger) loggerCache.get(name);
        if (childLogger != null) {
            return childLogger;
        }
        String childName;
        while (true) {
            // 获取java整体流程的“.”位置,如com.iboxpay.Test
            // 会分别获得两个“.”的位置
            int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
            if (h == -1) {
                childName = name;
            } else {
                childName = name.substring(0, h);
            }
            i = h + 1;
            // 上面的流程会依次获得“.”的位置,如com.iboxpay.Test
            // 的h值分别为3和11
            synchronized (logger) {
                childLogger = logger.getChildByName(childName);
                if (childLogger == null) {
                    // 如果logger原来没有这个子节点则创建一个新的
                    childLogger = logger.createChildByName(childName);
                    // 放进缓存中并且size+1
                    loggerCache.put(childName, childLogger);
                    incSize();
                }
            }
            // 跳到子节点,继续判断
            logger = childLogger;
            // 如果com.iboxpay.Test是从12开始的,那么h将会是-1,代表以遍历完成
            if (h == -1) {
                return childLogger;
            }
        }
        // 如com.iboxpay.Test路径将会创建三个logger,分别是com,com.iboxpay
        // 和com.iboxpay.Test,各个路径都会创建对应的logger,因此搭配
        // LoggerAction就可以实现对不同的包和类完成精确的级别控制
    }
    private void incSize() {
        size++;
    }
}

可以看到其实现了ILoggerFactory接口,因此在LoggerFactory中的getLogger方法中获取到的iLoggerFactory对象实际上就是LoggerContext类型,getLogger方法源码如下:

public final class LoggerFactory {
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
}

因此,对于这个方法而言整个流程将是在getILoggerFactory方法中完成Logback上下文的初始化,而创建Logger将是在iLoggerFactory.getLogger()中完成,接下来我们仔细分析一下创建Logger的流程。

看到LoggerContext类默认构造函数,这里面默认创建了一个Logger,名字为ROOT,默认的日志级别是DEBUG,是不是很熟悉?

接下来看到getLogger方法的流程,其流程分析写在了代码中,看代码即可。

至此,Logback的自启动初始化流程便已完成,经过此流程后便完成了XML文件的读取解析(如果有的话),并且也获得了所有已创建Logger的层级关系。后续再获取Logger时只需要从Logback的缓存中获取即可。

 

 

你可能感兴趣的:(Java第三方集成框架,#,Logback日志框架,java,logback,slf4j)