介绍

SLF4J(Simple Logging Facade for Java)是一个简单的日志门面框架,它允许应用程序使用单一的 API 进行日志记录,而不用关心具体的日志实现。SLF4J 提供了一个简单的抽象层,可以在运行时绑定到不同的日志框架,比如 logback、log4j 等。

下面是官方对于框架的一个介绍图,浅蓝色是抽象日志 API、蓝色是原生 slf4j-api 的实现,墨绿是适配层,因为有些框架比如 reload4j、log4j 等,并没有对原生 slf4j-api 的实现,所以官方提供了这样的适配层。应用只需要调用 slf4j 的 API,而不用关心日志框架的具体实现。

SLF4J-API 源码总览

- org.slf4j
---- event  # 包含 SLF4J 的事件类,用于表示日志记录事件。
---- helpers  # 包含了一些实用工具类,用于帮助开发人员更方便地使用 SLF4J 记录日志。
---- spi  # 下游需要提供的接口 
---- ...

event 包

提供了一个基本的日志事件实现 SubstituteLoggingEvent,以及 EventRecodingLogger,用于在具体日志框架初始化期间,框架内部有日志记录时,作为临时的日志记录。issue 参见

    static SLF4JServiceProvider getProvider() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;  // 这里将状态置为ONGOING_INITIALIZATION
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return PROVIDER;
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_SERVICE_PROVIDER;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        // 如果在初始化期间有其它调用进来,使用SUBST_PROVIDER,也就是SubstituteServiceProvider,底层是使用EventRecodingLogger
        case ONGOING_INITIALIZATION:  
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_PROVIDER;
        }
        throw new IllegalStateException("Unreachable code");
    }

在初始完成后,会将这部分记录的日志,重新用找到的具体实现进行重放。

    private static void postBindCleanUp() {
        fixSubstituteLoggers();  // 设置具体logger
        replayEvents();  // 使用具体logger重放期间记录的日志
        // release all resources in SUBST_FACTORY
        SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
    }

helpers 包

  • BasicMarker

对marker的基本实现,主要就是打印关联的 marker 名字作为日志上下文的补充。在 logback 中可以用 %marker 进行获取,也能通过 marker 对日志进行过滤。

    public String toString() {
        if (!this.hasReferences()) {
            return this.getName();
        }
        Iterator<Marker> it = this.iterator();
        Marker reference;
        StringBuilder sb = new StringBuilder(this.getName());
        sb.append(' ').append(OPEN);
        while (it.hasNext()) {
            reference = it.next();
            sb.append(reference.getName());
            if (it.hasNext()) {
                sb.append(SEP);
            }
        }
        sb.append(CLOSE);

        return sb.toString();
    }
  • MessageFormatter

日志的格式化类

    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) {
        // 获取传参列表中的最后一个参数,判断是否是异常对象
        Throwable throwableCandidate = MessageFormatter.getThrowableCandidate(argArray);
        Object[] args = argArray;
        if (throwableCandidate != null) {
            args = MessageFormatter.trimmedCopy(argArray);
        }
        // 根据占位符构造日志内容。
        // 比较有意思的是,替换是从左往右开始,循环次数由参数列表长度决定。
        // 这种性质使得,在参数列表个数个占位符后的内容,都不需要额外处理。
        return arrayFormat(messagePattern, args, throwableCandidate);
    }

    final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) {

        ......

        int i = 0;
        int j;
        // use string builder for better multicore performance
        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);

        int L;
        // 根据参数列表长度,进行查询
        for (L = 0; L < argArray.length; L++) {

            j = messagePattern.indexOf(DELIM_STR, i);  // 查找字符串"{}"

            if (j == -1) {
                // no more variables
                if (i == 0) { // this is a simple string
                    return new FormattingTuple(messagePattern, argArray, throwable);
                } else { // add the tail string which contains no variables and return
                    // the result.
                    sbuf.append(messagePattern, i, messagePattern.length());
                    return new FormattingTuple(sbuf.toString(), argArray, throwable);
                }
            } else {
                // 前一个字符是否是转义字符'\'
                if (isEscapedDelimeter(messagePattern, j)) {
                    // 是单个转义字符'\',那么就删除转义字符,添加字符串"{}"到内容中
                    // log.info("\\{} {} test", "hello") -> "{} hello test"
                    if (!isDoubleEscaped(messagePattern, j)) {
                        L--; // DELIM_START was escaped, thus should not be incremented
                        sbuf.append(messagePattern, i, j - 1);
                        sbuf.append(DELIM_START);
                        i = j + 1;
                    } else {
                        // The escape character preceding the delimiter start is
                        // itself escaped: "abc x:\\{}"
                        // we have to consume one backward slash
                        // log.info("\\\\{}", "place") -> "\place"
                        sbuf.append(messagePattern, i, j - 1);
                        deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
                        i = j + 2;
                    }
                } else {
                    // normal case
                    sbuf.append(messagePattern, i, j);
                    deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Object>());
                    i = j + 2;
                }
            }
        }
        // append the characters following the last {} pair.
        sbuf.append(messagePattern, i, messagePattern.length());
        return new FormattingTuple(sbuf.toString(), argArray, throwable);
}