logback源码分析-1.初始化

源码基于logback 1.1.7

首先看个demo:

根据类名获取Logger,这里是用slf4j提供的工厂来获取logger的:

  private static final Logger logger = LoggerFactory.getLogger("className");

进入getLogger方法:

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

首先获取ILoggerFactory,然后根据name获取Logger。

getILoggerFactory方法:

  public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
      INITIALIZATION_STATE = ONGOING_INITIALIZATION;
      //执行初始化
      performInitialization();
    }
    switch (INITIALIZATION_STATE) {
      //初始化成功则调用logback的StaticLoggerBinder返回LoggerFactory
      case SUCCESSFUL_INITIALIZATION:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
      //没有找到StaticLoggerBinder这个类  
      case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
      //版本不对,或者StaticLoggerBinder包路径不对,或者其他    
      case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
      //线程A已经快执行完performInitialization的时候,INITIALIZATION_STATE设置成SUCCESSFUL_INITIALIZATION,
      //线程B刚开始调用performInitialization,又将INITIALIZATION_STATE修改成ONGOING_INITIALIZATION
      //线程A执行到switch代码,就会跳转到这里,返回TEMP_FACTORY
      //TEMP_FACTORY如其名,调用它的getLogger方法获取到SubstituteLogger。
      //使用SubstituteLogger的info、error等方法打印日志的时候会判断它的_delegate属性是否为空
      //一开始_delegate为空,返回一个临时的NOPLogger.NOP_LOGGER实例,该实例什么也不做
      //后期performInitialization方法中会调用fixSubstitutedLoggers方法,重新设置_delegate为正确的logger。
      case ONGOING_INITIALIZATION:
        // support re-entrant behavior.
        // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
        return TEMP_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
  }

下面看看执行初始化的方法 performInitialization:

private final static void performInitialization() {
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
      //版本检查
      versionSanityCheck();
    }
  }

private final static void bind() {
    try {
      //查找org/slf4j/impl/StaticLoggerBinder.class,可能存在多个
      Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
      reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
      // the next line does the binding
      //如果没有StaticLoggerBinder class这里调用便会异常
      StaticLoggerBinder.getSingleton();
      //设置状态为成功初始化
      INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
      //如果StaticLoggerBinder class个数大于1个,则进行上报实际绑定的类型
      reportActualBinding(staticLoggerBinderPathSet);
      //修复并发调用getILoggerFactory时出现状态为ONGOING_INITIALIZATION且获取到的logger为TEMP_FACTORY的问题
      //解决方法是设置每个SubstituteLogger的_delegate为正确的logger
      fixSubstitutedLoggers();
    } catch (NoClassDefFoundError ncde) {
      String msg = ncde.getMessage();
      if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
        INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
        Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
        Util.report("Defaulting to no-operation (NOP) logger implementation");
        Util.report("See " + NO_STATICLOGGERBINDER_URL
                + " for further details.");
      } else {
        failedBinding(ncde);
        throw ncde;
      }
    } catch (java.lang.NoSuchMethodError nsme) {
      String msg = nsme.getMessage();
      if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
        INITIALIZATION_STATE = FAILED_INITIALIZATION;
        Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
        Util.report("Your binding is version 1.5.5 or earlier.");
        Util.report("Upgrade your binding to version 1.6.x.");
      }
      throw nsme;
    } catch (Exception e) {
      failedBinding(e);
      throw new IllegalStateException("Unexpected initialization failure", e);
    }
  }

执行完performInitialization方法后,状态被设置成SUCCESSFUL_INITIALIZATION,然后执行StaticLoggerBinder.getSingleton().getLoggerFactory()。

在这之前先看看StaticLoggerBinder的静态代码块:

private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private static Object KEY = new Object();
static {
        //StaticLoggerBinder单例调用init方法
        SINGLETON.init();
    }
private boolean initialized = false;
//生成默认的LoggerContext
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;
    }

void init() {
        try {
            try {
                //创建ContextInitializer,调用autoConfig解析配置文件
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                Util.report("Failed to auto configure default logger context", je);
            }
            // logback-292
            //打印logback内部error、warn信息
            //如果没有配置StatusListener,则使用StatusPrinter的stdout方式打印
            //配置StatusListener的代码实现在ContextInitializer#autoConfig()方法第一行
            //内部产生的error、warn等日志在StaticLoggerBinder.defaultLoggerContext.sm中维护的。
            //比如之前autoConfig方法中解析xml配置时出现error或者warn日志会在sm中存储,然后在此处打印
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
            }
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            //initialized初始为false,init成功设置成true
            initialized = true;
        } catch (Throwable t) {
            // we should never get here
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }

接下来分析ContextInitializer.autoConfig:

public void autoConfig() throws JoranException {
        //解析在环境变量中配置的StatusListener实现类,如果配置的话生成相应的实例,
        //调用其start方法,上报过去一段时间logback内部日志
        StatusListenerConfigHelper.installIfAsked(loggerContext);
        //获取logback配置文件URL
        URL url = findURLOfDefaultConfigurationFile(true);
        if (url != null) {
            //解析logback配置文件,核心
            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(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
                                    .getCanonicalName() : "null"), e);
                }
            } else {
                //设置默认的config,后期日志直接输出到控制台
                BasicConfigurator basicConfigurator = new BasicConfigurator();
                basicConfigurator.setContext(loggerContext);
                basicConfigurator.configure(loggerContext);
            }
        }
    }

下面分析下ContextInitializer.configureByResource(url):

public void configureByResource(URL url) throws JoranException {
        if (url == null) {
            throw new IllegalArgumentException("URL argument cannot be null");
        }
        final String urlString = url.toString();
        //logback配置是groovy文件
        if (urlString.endsWith("groovy")) {
            if (EnvUtil.isGroovyAvailable()) {
                // avoid directly referring to GafferConfigurator so as to avoid
                // loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
                GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
            } else {
                StatusManager sm = loggerContext.getStatusManager();
                sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", loggerContext));
            }
        } else if (urlString.endsWith("xml")) {
            //logback配置文件是xml。我们主要分析该情况下的配置
            //首先创建一个配置器,设置好loggerContext后,根据url进行配置解析
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(loggerContext);
            configurator.doConfigure(url);
        } else {
            throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml");
        }
    }

JoranConfigurator.doConfigure(url)代码如下:

public final void doConfigure(URL url) throws JoranException {
        InputStream in = null;
        try {
            //将该url添加到watch list中
            informContextOfURLUsedForConfiguration(getContext(), url);
            URLConnection urlConnection = url.openConnection();
            // per http://jira.qos.ch/browse/LBCORE-105
            // per http://jira.qos.ch/browse/LBCORE-127
            //如果设置成true的话,当这个配置文件在一个jar中的时候,jar被锁住。如果应用需要重新加载的话会出现问题。
            //所以设置成false。
            urlConnection.setUseCaches(false);

            in = urlConnection.getInputStream();
            //将logback配置转换成流进行解析
            doConfigure(in);
        } 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);
                }
            }
        }
    }
    

//将InputStream转换成sax的InputSource对象,然后调用doConfigure进行解析
public final void doConfigure(InputStream inputStream) throws JoranException {
        doConfigure(new InputSource(inputStream));
    }



// this is the most inner form of doConfigure whereto other doConfigure
// methods ultimately delegate
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);
        //将xml的<tag>bodyStr</tag>解析成StartEvent,BodyEvent,EndEvent,保存到recorder.saxEventList中
        //如果解析期间遇到warn、error、fatalError情况则会生成信息存入StaticLoggerBinder.defaultLoggerContext.sm中
        //后期将会将这些日志输出
        recorder.recordEvents(inputSource);
        //根据xml每个element对应的event来进行解析
        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);
        }
    }
    
public void doConfigure(final List<SaxEvent> eventList) throws JoranException {
        //将大部分的element path包装成ElementSelector并映射一个Action,比如xml中configuration/logger element path映射成LoggerAction
        //当解析到<configuration><logger>时会触发LoggerAction的begin方法
        //当解析到<configuration><logger>bodyStr 时会触发LoggerAction的body方法,当然logger element是没有body的
        //当解析到<configuration><logger>bodyStr</logger> 时会触发LoggerAction的end方法
        //action的映射关系存在JoranConfigurator#interpreter中
        buildInterpreter();
        // disallow simultaneous configurations of the same context
        synchronized (context.getConfigurationLock()) {
            //解析eventList
            interpreter.getEventPlayer().play(eventList);
        }
    } 
    

如下是一个简单的logback.xml demo:

<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://www.padual.com/java/logback.xsd">

    <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/Users/apple/Documents/UNKONESERVER/warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <fileNamePattern>/Users/apple/Documents/UNKONESERVER/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的保存天数 -->
            <maxHistory>2</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.logback.test" level="debug"
            additivity="false">
        <appender-ref ref="WARN" />
    </logger>

    <root level="debug">
    </root>

</configuration>

转换成SaxEvent list便是这个样子:

eventList = {ArrayList@982}  size = 26
 0 = {StartEvent@999} "StartEvent(configuration)  [2,63]"
 1 = {StartEvent@1000} "StartEvent(appendername="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender")  [4,83]"
 2 = {StartEvent@1001} "StartEvent(file)  [5,15]"
 3 = {BodyEvent@1002} "BodyEvent(/Users/apple/Documents/UNKONESERVER/warn.log)5,61"
 4 = {EndEvent@1003} "  EndEvent(file)  [5,66]"
 5 = {StartEvent@1004} "StartEvent(rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy")  [6,83]"
 6 = {StartEvent@1005} "StartEvent(fileNamePattern)  [8,30]"
 7 = {BodyEvent@1006} "BodyEvent(/Users/apple/Documents/UNKONESERVER/warn.%d{yyyy-MM-dd}.log)8,91"
 8 = {EndEvent@1007} "  EndEvent(fileNamePattern)  [8,107]"
 9 = {StartEvent@1008} "StartEvent(maxHistory)  [10,25]"
 10 = {BodyEvent@1009} "BodyEvent(2)10,28"
 11 = {EndEvent@1010} "  EndEvent(maxHistory)  [10,39]"
 12 = {EndEvent@1011} "  EndEvent(rollingPolicy)  [11,25]"
 13 = {StartEvent@1012} "StartEvent(encoder)  [12,18]"
 14 = {StartEvent@1013} "StartEvent(pattern)  [13,22]"
 15 = {BodyEvent@1014} "BodyEvent(%msg%n)13,30"
 16 = {EndEvent@1015} "  EndEvent(pattern)  [13,38]"
 17 = {EndEvent@1016} "  EndEvent(encoder)  [14,19]"
 18 = {EndEvent@1017} "  EndEvent(appender)  [15,16]"
 19 = {StartEvent@1018} "StartEvent(loggername="com.logback.test" level="debug" additivity="false")  [18,32]"
 20 = {StartEvent@1019} "StartEvent(appender-refref="WARN")  [19,36]"
 21 = {EndEvent@1020} "  EndEvent(appender-ref)  [19,36]"
 22 = {EndEvent@1021} "  EndEvent(logger)  [20,14]"
 23 = {StartEvent@1022} "StartEvent(rootlevel="debug")  [23,25]"
 24 = {EndEvent@1023} "  EndEvent(root)  [25,12]"
 25 = {EndEvent@1024} "  EndEvent(configuration)  [27,17]"

对应26个event。 下面分析EventPlayer中play方法如何对event list进行遍历处理。 很清晰的看到event分成三种:StartEvent、BodyEvent、EndEvent。这个三种最终会调用对应action的begin、body、end方法:

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);
            }

        }
    }
  • satrtEvent
public void startElement(StartEvent se) {
        //设置event在xml中的locator信息,比如在多少行多少列
        setDocumentLocator(se.getLocator());
        //处理start event
        startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
    }
    
private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
        //首先选择localName为tagName,如果localName为空则选择qName
        String tagName = getTagName(localName, qName);
        //将这个startEvent的tagName推入elementPath栈中,后期处理对应的endEvent时,触发endElement方法弹出该tagName
        elementPath.push(tagName);

        if (skip != null) {
            // every startElement pushes an action list
            pushEmptyActionList();
            return;
        }
        //根据elementPath查找相应的action list,
        //比如elementPath推入了两个数据[configuration][appender],则会找到AppenderAction。
        //Interpreter中ruleStore并不维护所有elementPath对应的action,
        //比如[configuration][appender][file],在ruleStore中找不到对应的action。
        //logback提供了两种隐式action:NestedComplexPropertyIA、NestedBasicPropertyIA,
        //1.[configuration][appender][file]对应的action是NestedBasicPropertyIA,
        //因为它属于简单的内嵌式action,file属于appender的一个属性,而file element没有内嵌element,只有一个body。
        //2.[configuration][appender][rollingPolicy]对应的action是NestedComplexPropertyIA,
        //因为它属于复杂的内嵌式action,rollingPolicy属于appender的一个属性,但是,rollingPolicy element还拥有自己的内嵌element
        List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
        if (applicableActionList != null) {
            //将action list推入actionlistStacl栈中,后续会用到
            actionListStack.add(applicableActionList);
            //遍历applicableActionList,触发每个action的begin方法,正常applicableActionList中只有一个元素
            callBeginAction(applicableActionList, tagName, atts);
        } else {
            // every startElement pushes an action list
            pushEmptyActionList();
            String errMsg = "no applicable action for [" + tagName + "], current ElementPath  is [" + elementPath + "]";
            cai.addError(errMsg);
        }
    }

void callBeginAction(List<Action> applicableActionList, String tagName, Attributes atts) {
        if (applicableActionList == null) {
            return;
        }

        Iterator<Action> i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = (Action) i.next();
            // now let us invoke the action. We catch and report any eventual
            // exceptions
            try {
                //真正的处理start event的逻辑,即该element path对应action的begin方法
                action.begin(interpretationContext, tagName, atts);
            } catch (ActionException e) {
                skip = elementPath.duplicate();
                cai.addError("ActionException in Action for tag [" + tagName + "]", e);
            } catch (RuntimeException e) {
                skip = elementPath.duplicate();
                cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
            }
        }
    }    
  • bodyEvent
public void characters(BodyEvent be) {
         
        setDocumentLocator(be.locator);
        //获取body文本
        String body = be.getText();
        List<Action> applicableActionList = actionListStack.peek();

        if (body != null) {
            body = body.trim();
            if (body.length() > 0) {
                // System.out.println("calling body method with ["+body+ "]");
                callBodyAction(applicableActionList, body);
            }
        }
    }

private void callBodyAction(List<Action> applicableActionList, String body) {
        if (applicableActionList == null) {
            return;
        }
        Iterator<Action> i = applicableActionList.iterator();

        while (i.hasNext()) {
            Action action = i.next();
            try {
                //触发该element path对应action的body方法
                action.body(interpretationContext, body);
            } catch (ActionException ae) {
                cai.addError("Exception in end() methd for action [" + action + "]", ae);
            }
        }
    }
  • endEvent
public void endElement(EndEvent endEvent) {
        setDocumentLocator(endEvent.locator);
        endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
    }

private void endElement(String namespaceURI, String localName, String qName) {
        // given that an action list is always pushed for every startElement, we
        // need
        // to always pop for every endElement
        List<Action> applicableActionList = (List<Action>) actionListStack.pop();

        if (skip != null) {
            if (skip.equals(elementPath)) {
                skip = null;
            }
        } else if (applicableActionList != EMPTY_LIST) {
            callEndAction(applicableActionList, getTagName(localName, qName));
        }

        // given that we always push, we must also pop the pattern
        //把在startElement方法中推入的元素弹出来
        elementPath.pop();
    }

private void callEndAction(List<Action> applicableActionList, String tagName) {
        if (applicableActionList == null) {
            return;
        }

        // logger.debug("About to call end actions on node: [" + localName + "]");
        Iterator<Action> i = applicableActionList.iterator();

        while (i.hasNext()) {
            Action action = i.next();
            // now let us invoke the end method of the action. We catch and report
            // any eventual exceptions
            try {
                //触发当前element path对应action的end方法
                action.end(interpretationContext, tagName);
            } catch (ActionException ae) {
                // at this point endAction, there is no point in skipping children as
                // they have been already processed
                cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
            } catch (RuntimeException e) {
                // no point in setting skip
                cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
            }
        }
    }
    

转载于:https://my.oschina.net/u/913896/blog/916687

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值