Logback 소스 코드 분석

매일 개발 단계에서 자주 인쇄 로깅 프로그램이 수행하거나 문제, 다음과 같은 코드의 많은 문제를 해결하지만, 그것을 구현하는 방법이다?

package chapters;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 省略...
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
logger.info(" {} is best player in world", "Greizmann");

이 논문은 상기 코드를 달성하기 Logback 로깅 구조를 분석한다.

SLF4J

이제 일반적으로 로깅 프레임 워크 log4j에, log4j2 7 월 (공통 로그)와 logback에 사용됩니다. 프로젝트에서 사용하는 경우 7월이며, 지금 java.util.logging의 패키지 로거에 대한 직접 참조, 당신은 많은 코드를 수정해야하는 경우이 어려운 일을 해결하기 위해, log4j에 함께 변경하려면, Ceki Gülcü 위대한 하나님에 대한 SLF4J (단순 로깅 외관을 개발 자바). SLF4J 많은 로깅 프레임 워크는 로그가 단지 해당 단지의 대체 및 추가 어댑터에 대응하는 필요, SLF4J로 구현 외관 추상적 인 인터페이스는 로그를 전환하고 싶어합니다.

출처 : 잘 알려진 로깅 시스템 밖으로 설계하는 방법이다?

우리의 코드는 리드 SLF4J 패키지에 시작하는 이유는 그림에서 당신은 볼 수 있습니다. 수동 알리의 개발에

강제 : 응용 프로그램은 직접 API의 로깅 시스템 (log4j에, logback)를 사용하지만, 오히려 SLF4J 로깅 프레임 워크 API의 사용에 의존하지. 외관 패턴을 사용하여 프레임 워크를 기록하면, 각 클래스에 대한 처리 방법을 단일성을 유지하고 로그에 도움이된다.

Logback은 SLF4J 적은 중간 적응 계층, Logback도 Ceki Gülcü 신 개발을 깨달았다.

로거 및이 appender 및 레이아웃

세 가지 주요 카테고리 로거, 펜더와 레이아웃을 Logback. 이 세 가지 구성 요소가 함께 레벨에 따라 다른 장소에서 개발 및 인쇄 로그 로그 메시지의 입력 우리의 역할을 충족합니다.

나무꾼

ch.qos.logback.classic.Logger 클래스 구조 :

LoggerContext에 따라 로거, 생산 로거에 대한 책임 LoggerContext, 나무와 같은 계층 구조에 의해 관리된다. 로거의 로그 레벨과 현재 노드의 레벨 값을 유지한다. 세대 (레벨)에 의해 로거는 같은 수준을 기록 할 수있는 능력을 상속 "".에 이름이 chapters.LogbackTest 로그 레벨이없는 경우, 상속 부모 장이 수준을 기록합니다. 모든 로그는 조상의 ROOT 이름 로거, 기본 레벨 DEBUG입니다. 부모 클래스의 로그 수준을 고려하지 않고 현재 노드의 로그 레벨을 설정합니다. 로거 활성화 및 제어 로그를 비활성화하여 수준을 기록합니다. 로그 수준 TRACE의 < DEBUG < INFO는 < WARN < ERROR를 .

우리는 구성 라벨을 해당 결합 된 구성 파일 로거 속성을 보면 다음 :

<configuration>
  <turboFilter class="ch.qos.logback.classic.turbo.MDCFilter">
    <MDCKey>username</MDCKey>
    <Value>sebastien</Value>
    <OnMatch>ACCEPT</OnMatch>
  </turboFilter>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>/Users/wolf/study/logback/logback-examples/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>
 
  <logger name="chapters.LogbackTest" level="DEBUG"></logger>

  <root>
    <appender-ref ref="FILE"/>
  </root>
</configuration>

이름 : 로거 태그 이름 속성 값입니다.

수준 : 속성 값 로거 태그 수준입니다.

로거 객체의 부모 클래스 캡슐화 "장"및 "장"부모 클래스 "ROOT"부모입니다.

AAI : 펜더-REF 태그 및 해당 FileAppender 구현 클래스의 객체. 태그가 펜더-REF가 아닌 경우 null입니다.

loggerContext : 이러한 터보 필터와 같은 필터를 유지한다.

펜더

펜더 역할은 로그의 출력 대상을 제어하는 ​​것입니다. 로그 출력 대상은 당신의 콘솔, 파일 출력, 원격 소켓 서버, MySQL은, 포스트 그리 SQL, Oracle 또는 다른 데이터베이스, JMS, 원격 UNIX 시스템 로그 데몬을 기록 할 수 있습니다, 다양 화된다. 로그는 여러 목적지로 출력 할 수 있습니다. 으로

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>/Users/wolf/study/logback/logback-examples/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root>
    <appender-ref ref="STDOUT" />
    <appender-ref ref="FILE"/>
  </root>
</configuration>

콘솔에 출력 XML 설정 파일과 로그 myApp.log.

레이아웃 / 인코더

이 로거 및 두 개의 구성 요소 이상이 appender는 로그 대상에 출력 된, 그러나 로그 하드 우리 인간의 비우호적에 인쇄가 읽을 수 있도록. 운명은 레이아웃으로 아름답게 또는 그것에 대해 출력 포맷 인코더를 기록, 아름다운 것입니다. 인코더는 logback 0.9.19 버전에 도입했다. 이전에는 펜더 따라 레이아웃의 대부분은 문자열로, 다음을 통해 로그 이벤트를 변환하는 java.io.Writer쓰기. 이전 버전에서 사용자의 요구에 FileAppender내장 된 하나 PatternLayout. 이후 버전 0.9.19,에서 FileAppender뿐만 아니라 하위 수준의 인코더보다는 레이아웃의 필요성.

근원

생성 로거

Logger logger = LoggerFactory.getLogger(LogbackTest.class);

다음으로, 우리는 로거에 따라 초기화하는 소스 코드를 분석 할 수 있습니다. 또는 오른쪽으로 분석 소스 전에 타이밍도에 이전 규칙 인터페이스 호출에 따라한다.

第 步 : org.slf4j.LoggerFactory # getLogger (java.lang.String의)

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

ILoggerFactory, 즉 LoggerContext 가져옵니다. 그런 다음에서 개체는 로거에 도착합니다.

3 단계 : org.slf4j.LoggerFactory # getILoggerFactory

public static ILoggerFactory getILoggerFactory() {
    return getProvider().getLoggerFactory();
}

4 단계 : org.slf4j.LoggerFactory #를 GetProvider

static SLF4JServiceProvider getProvider() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                performInitialization();
            }
        }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return PROVIDER;
    case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
        return SUBST_PROVIDER;
    }
    throw new IllegalStateException("Unreachable code");
}

초기화 SLF4JServiceProvider, 즉 LogbackServiceProvider 객체입니다. 그런 다음, 성공했을 경우는 반환 공급자를 초기화 상태를 확인합니다.

第5步:org.slf4j.LoggerFactory#performInitialization

private final static void performInitialization() {
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
        versionSanityCheck();
    }
}

第6步:org.slf4j.LoggerFactory#bind

private final static void bind() {
    try {
        // 加载 SLF4JServiceProvider 
        List<SLF4JServiceProvider> providersList = findServiceProviders();
        reportMultipleBindingAmbiguity(providersList);
        if (providersList != null && !providersList.isEmpty()) {
           PROVIDER = providersList.get(0);
           // SLF4JServiceProvider.initialize()
           PROVIDER.initialize();
           INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(providersList);
            fixSubstituteLoggers();
            replayEvents();
            SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
        } else {
            // 省略代码。。。
        }
    } catch (Exception e) {
        // 失败,设置状态值,上报
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
    }
}

通过ServiceLoader加载LogbackServiceProvider,然后进行初始化相关字段。初始化成功后把初始化状态设置成功状态值。

第7步:ch.qos.logback.classic.spi.LogbackServiceProvider#initialize

public void initialize() {
    // 初始化默认的loggerContext
    defaultLoggerContext = new LoggerContext();
    defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
    initializeLoggerContext();
    markerFactory = new BasicMarkerFactory();
    mdcAdapter = new LogbackMDCAdapter();
}

创建名字为default的LoggerContext对象,并初始化一些字段默认值。

ch.qos.logback.classic.LoggerContext#LoggerContext

public LoggerContext() {
    super();
    this.loggerCache = new ConcurrentHashMap<String, Logger>();
    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<String>();
}

初始化LoggerContext时设置了ROOT的Logger,日志级别为DEBUG。

第8步:ch.qos.logback.classic.spi.LogbackServiceProvider#initializeLoggerContext

private void initializeLoggerContext() {
    try {
        try {
            new ContextInitializer(defaultLoggerContext).autoConfig();
        } catch (JoranException je) {
            Util.report("Failed to auto configure default logger context", je);
        }
        // 省略代码。。。
    } catch (Exception t) { // see LOGBACK-1159
        Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
    }
}

把第7步初始化好的LoggerContext当做参数传入ContextInitializer,构建其对象。然后解析配置文件。

第9步:ch.qos.logback.classic.util.ContextInitializer#autoConfig

public void autoConfig() throws JoranException {
    StatusListenerConfigHelper.installIfAsked(loggerContext);
    // (1) 从指定路径获取
    URL url = findURLOfDefaultConfigurationFile(true);
    if (url != null) {
        configureByResource(url);
    } else {
        // (2) 从运行环境中获取
        Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
        if (c != null) {
            // 省略代码。。。
        } else {
            // (3)设置默认的
            BasicConfigurator basicConfigurator = new BasicConfigurator();
            basicConfigurator.setContext(loggerContext);
            basicConfigurator.configure(loggerContext);
        }
    }
}

首先从指定的路径获取资源URL,如果存在就进行解析;如果不存在再从运行环境中获取配置;如果以上都没有最后会构建一个BasicConfigurator当作默认的。

ch.qos.logback.classic.util.ContextInitializer#findURLOfDefaultConfigurationFile

public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
    ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
    // 启动参数中获取
    URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
    if (url != null) {
        return url;
    }
    // logback-test.xml
    url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
    if (url != null) {
        return url;
    }
    //logback.groovy
    url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
    if (url != null) {
        return url;
    }
    // logback.xml
    return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}

先从启动参数中查找logback.configurationFile参数值,如果没有再从classpath中一次查找logback-test.xml -> logback.groovy -> logback.xml 。由此可知文件的优先级是 启动参数 -> logback-test.xml -> logback.groovy -> logback.xml

第10步:ch.qos.logback.classic.util.ContextInitializer#configureByResource

public void configureByResource(URL url) throws JoranException {
    if (url == null) {
        throw new IllegalArgumentException("URL argument cannot be null");
    }
    final String urlString = url.toString();
    if (urlString.endsWith("groovy")) {
         // 省略代码。。。
    } else if (urlString.endsWith("xml")) {
        JoranConfigurator configurator = new JoranConfigurator();
        configurator.setContext(loggerContext);
        configurator.doConfigure(url);
    } else {
        // 省略代码。。。
    }
}

根据文件后缀判断是 groovy或者xml,然后交给不同的配置解析器处理。这里也是把第7步中的LoggerContext传进去,继续封装它的字段值。

第12步:ch.qos.logback.core.joran.GenericConfigurator#doConfigure(org.xml.sax.InputSource)

public final void doConfigure(final InputSource inputSource) throws JoranException {

    long threshold = System.currentTimeMillis();
    SaxEventRecorder recorder = new SaxEventRecorder(context);
    recorder.recordEvents(inputSource);
    // 处理配置文件,封装到 LoggerContext 中
    playEventsAndProcessModel(recorder.saxEventList);
    StatusUtil statusUtil = new StatusUtil(context);
    if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
        registerSafeConfiguration(recorder.saxEventList);
    }
}

真正解析配置文件的逻辑在playEventsAndProcessModel方法中,这里就不展开分析了。到这一步LoggerContext基本初始化完成了。

第13步:ch.qos.logback.classic.LoggerContext#getLogger(java.lang.String)

@Override
public Logger getLogger(final String name) {
    // 省略代码。。。 
    if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
        return root;
    }

    int i = 0;
    Logger logger = root;
    // 从缓存中获取, 有直接返回
    Logger childLogger = (Logger) loggerCache.get(name);
    if (childLogger != null) {
        return childLogger;
    }

    // if the desired logger does not exist, them create all the loggers
    // in between as well (if they don't already exist)
    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;
        }
    }
}

经过前面漫长的对LoggerContext进行初始化工作,这一步就是从LoggerContext获取Logger对象。如果缓存中直接返回。否则通过“.”分代构建层次结构。

日志执行步骤

上一节Logger创建完成,接下来分析一下打日志的流程。

logger.info(" {} is best player in world", "Greizmann");

第1步:ch.qos.logback.classic.Logger#info(java.lang.String, java.lang.Object)

public void info(String format, Object arg) {
    filterAndLog_1(FQCN, null, Level.INFO, format, arg, null);
}

把接口的日志级别(Level.INFO)传到下一个方法。

第2步:ch.qos.logback.classic.Logger#filterAndLog_1

private void filterAndLog_1(final String localFQCN, final Marker marker, final Level level, final String msg, final Object param, final Throwable t) {
    // 先通过turboFilter过滤
    final FilterReply decision = loggerContext.getTurboFilterChainDecision_1(marker, this, level, msg, param, t);
    // 判断日志级别
    if (decision == FilterReply.NEUTRAL) {
        if (effectiveLevelInt > level.levelInt) {
            return;
        }
    } else if (decision == FilterReply.DENY) {
        return;
    }

    buildLoggingEventAndAppend(localFQCN, marker, level, msg, new Object[] { param }, t);
}

如果TurboFilter过滤器存在就会执行相关操作,并返回FilterReply。如果结果是FilterReply.DENY本条日志消息直接丢弃;如果是FilterReply.NEUTRAL会继续判断日志级别是否在该方法级别之上;如果是FilterReply.ACCEPT直接跳到下一步。

第3步:ch.qos.logback.classic.Logger#buildLoggingEventAndAppend

private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params, final Throwable t) {
    LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
    le.setMarker(marker);
    callAppenders(le);
}

创建了LoggingEvent对象,该对象包含日志请求所有相关的参数,请求的 logger,日志请求的级别,日志信息,与日志一同传递的异常信息,当前时间,当前线程,以及当前类的各种信息和 MDC。其实打印日志就是一个事件,所以这个对象是相关重要,下面全部是在操作该对象。

第4步:ch.qos.logback.classic.Logger#callAppenders

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

第5步:ch.qos.logback.classic.Logger#appendLoopOnAppenders

private int appendLoopOnAppenders(ILoggingEvent event) {
    if (aai != null) {
        return aai.appendLoopOnAppenders(event);
    } else {
        return 0;
    }
}

从当前Logger到父节点遍历,直到AppenderAttachableImpl不为空(有appender-ref 标签)。

第6步:ch.qos.logback.core.spi.AppenderAttachableImpl#appendLoopOnAppenders

public int appendLoopOnAppenders(E e) {
    int size = 0;
    final Appender<E>[] appenderArray = appenderList.asTypedArray();
    final int len = appenderArray.length;
    for (int i = 0; i < len; i++) {
        appenderArray[i].doAppend(e);
        size++;
    }
    return size;
}

如果设置了多个日志输出目的地,这里就是循环调用对应的Appender进行输出。

第7步:ch.qos.logback.core.UnsynchronizedAppenderBase#doAppend

public void doAppend(E eventObject) {
    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;
        }

        this.append(eventObject);

    } catch (Exception e) {
        if (exceptionCount++ < ALLOWED_REPEATS) {
            addError("Appender [" + name + "] failed to append.", e);
        }
    } finally {
        guard.set(Boolean.FALSE);
    }
}

通过ThreadLocal控制递归导致的重复提交

第8步:ch.qos.logback.core.OutputStreamAppender#append

protected void append(E eventObject) {
    if (!isStarted()) {
        return;
    }

    subAppend(eventObject);
}

第9步:ch.qos.logback.core.OutputStreamAppender#subAppend

protected void subAppend(E event) {
    if (!isStarted()) {
        return;
    }
    try {
        if (event instanceof DeferredProcessingAware) {
            // 拼接日志信息(填充占位符),设置当前线程以及MDC等信息
            ((DeferredProcessingAware) event).prepareForDeferredProcessing();
        }
        byte[] byteArray = this.encoder.encode(event);
        writeBytes(byteArray);
    } catch (IOException ioe) {
        this.started = false;
        addStatus(new ErrorStatus("IO failure in appender", this, ioe));
    }
}

Encoder在这里惨淡登场,返回byte数组。

第10步:ch.qos.logback.core.encoder.LayoutWrappingEncoder#encode

public byte[] encode(E event) {
    String txt = layout.doLayout(event);
    return convertToBytes(txt);
}

Encoder先把LoggerEvent交给Layout,Layout组装日志信息,在每条信息后加上换行符。

第11步:ch.qos.logback.core.OutputStreamAppender#writeBytes

private void writeBytes(byte[] byteArray) throws IOException {
    if(byteArray == null || byteArray.length == 0)
        return;
    
    lock.lock();
    try {
        this.outputStream.write(byteArray);
        if (immediateFlush) {
            this.outputStream.flush();
        }
    } finally {
        lock.unlock();
    }
}

使用AQS锁控制并发问题。这也是Logback性能不如 Log4j2的原因。后面有时间分析一下Log4j2。

本文到此结束了,还有两天就要放假了,祝大家新年快乐。

추천

출처www.cnblogs.com/wolf-bin/p/12213058.html