再看 Logback 源码

三大组件

Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。

Logger 类作为 logback-classic 模块的一部分。AppenderLayouts 接口作为 logback-core 的一部分。作为一个通用的模块,logback-core 没有 logger 的概念。

Appender 与 Layout

有选择的启用或者禁用日志的输出只是 logger 的一部分功能。logback 允许日志在多个地方进行输出。站在 logback 的角度来说,输出目的地叫做 appender。appender 包括console、file、remote socket server、MySQL、PostgreSQL、Oracle 或者其它的数据库、JMS、remote UNIX Syslog daemons 中。

一个 logger 可以有多个 appender。

logger 通过 addAppender 方法来新增一个 appender。对于给定的 logger,每一个允许输出的日志都会被转发到该 logger 的所有 appender 中去。换句话说,appender 从 logger 的层级结构中去继承叠加性。例如:如果 root logger 添加了一个 console appender,所有允许输出的日志至少会在控制台打印出来。如果再给一个叫做 L 的 logger 添加了一个 file appender,那么 L 以及 L 的子级 logger 都可以在文件和控制台打印日志。可以通过设置 additivity = false 来改写默认的设置,这样 appender 将不再具有叠加性。

通常,用户既想自定义日志的输出地,也想自定义日志的输出格式。通过给 appender 添加一个 layout 可以做到。layout 的作用是将日志格式化,而 appender 的作用是将格式化后的日志输出到指定的目的地。PatternLayout 能够根据用户指定的格式来格式化日志,类似于 C 语言的 printf 函数。

例:PatternLayout 通过格式化串 “%-4relative [%thread] %-5level %logger{32} - %msg%n” 会将日志格式化成如下结果:

176  [main] DEBUG manual.architecture.HelloWorld2 - Hello world.

底层流程

在介绍了基本的 logback 组件之后,我们准备介绍一下,当用户调用日志的打印方法时,logback 所执行的步骤。现在我们来分析一下当用户通过一个名为 com.wombat 的 logger 调用了 info() 方法时,logback 执行了哪些步骤。

第一步:获取过滤器链

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main1 {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Main1.class);
        logger.info("test:{}", 111);
    }
}

ch.qos.logback.classic.Logger

public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();

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

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

ch.qos.logback.classic.spi.TurboFilterList 这个位于 classic 包

public FilterReply getTurboFilterChainDecision(final Marker marker, final Logger logger, final Level level,
        final String format, final Object[] params, final Throwable t) {

    final int size = size();
    // if (size == 0) {
    // return FilterReply.NEUTRAL;
    // }
    if (size == 1) {
        try {
            TurboFilter tf = get(0);
            return tf.decide(marker, logger, level, format, params, t);
        } catch (IndexOutOfBoundsException iobe) {
            return FilterReply.NEUTRAL;
        }
    }

    Object[] tfa = toArray();
    final int len = tfa.length;
    for (int i = 0; i < len; i++) {
        // for (TurboFilter tf : this) {
        final TurboFilter tf = (TurboFilter) tfa[i];
        final FilterReply r = tf.decide(marker, logger, level, format, params, t);
        if (r == FilterReply.DENY || r == FilterReply.ACCEPT) {
          // 如果是 deny 或者 accept 直接返回
            return r;
        }
    }
    return FilterReply.NEUTRAL;
}
public enum FilterReply {
    DENY, NEUTRAL, ACCEPT;
}

微信公众号:CoderLi

第二步:Logger 级别过滤

如果返回的 Reply 是 neutral 则会根据级别进行过滤。如果是 deny 则直接返回

微信公众号:CoderLi

第三步:创建 LoggingEvent 对象

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

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.addMarker(marker);
    callAppenders(le);
}

微信公众号:CoderLi

从参数数组中提取异常

image-20221022221235596

public static final Throwable extractThrowable(Object[] argArray) {
    if (argArray == null || argArray.length == 0) {
        return null;
    }

    final Object lastEntry = argArray[argArray.length - 1];
    if (lastEntry instanceof Throwable) {
        return (Throwable) lastEntry;
    }
    return null;
}

提取最后一个参数

public static Object[] trimmedCopy(Object[] argArray) {
    if (argArray == null || argArray.length == 0) {
        throw new IllegalStateException("non-sensical empty or null argument array");
    }
    final int trimemdLen = argArray.length - 1;
    Object[] trimmed = new Object[trimemdLen];
    System.arraycopy(argArray, 0, trimmed, 0, trimemdLen);
    return trimmed;
}

重新赋值给这个参数数组

第四步:调用 appender

/**
 * Invoke all the appenders of this logger.
 * 
 * @param event The event to log
 */
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);
    }
}

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

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

微信公众号:CoderLi

这里有两个实现累、一个是同步的、这个正常是不会使用这个的了

微信公众号:CoderLi

微信公众号:CoderLi

我们直接看另一个的实现

/**
     * The guard prevents an appender from repeatedly calling its own doAppend
     * method.
     */
private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>();

public void doAppend(E eventObject) {

    if (Boolean.TRUE.equals(guard.get())) {
        return;
    }

    try {
        guard.set(Boolean.TRUE);
				............
        // 只要不是 deny 都可以继续
        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);
    }
}

getFilterChainDecision 这里也出现了一个 Filter 的接口

public FilterReply getFilterChainDecision(E event) {
    return fai.getFilterChainDecision(event);
}

// ch.qos.logback.core.spi.FilterAttachableImpl
public FilterReply getFilterChainDecision(E event) {

  final Filter<E>[] filterArrray = filterList.asTypedArray();
  final int len = filterArrray.length;

  for (int i = 0; i < len; i++) {
    final FilterReply r = filterArrray[i].decide(event);
    if (r == FilterReply.DENY || r == FilterReply.ACCEPT) {
      return r;
    }
  }

  // no decision
  return FilterReply.NEUTRAL;
}

微信公众号:CoderLi

这个 Filter 是位于 core 模块中的、每一个 Appender 都有自己的 FilterList

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

    subAppend(eventObject);
}

// ch.qos.logback.core.OutputStreamAppender
// RollingFileAppender 只是多了一个滚动文件、也就是新建新的日志文件
protected void subAppend(E event) {
  if (!isStarted()) {
    return;
  }
  try {
    // this step avoids LBCLASSIC-139
    if (event instanceof DeferredProcessingAware) {
      ((DeferredProcessingAware) event).prepareForDeferredProcessing();
    }
    writeOut(event);

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

第五步:格式化输出

protected void writeOut(E event) throws IOException {
    byte[] byteArray = this.encoder.encode(event);
    writeBytes(byteArray);
}

微信公众号:CoderLi

ch.qos.logback.core.encoder.LayoutWrappingEncoder

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

微信公众号:CoderLi

ch.qos.logback.classic.PatternLayout

public String doLayout(ILoggingEvent event) {
    if (!isStarted()) {
        return CoreConstants.EMPTY_STRING;
    }
    return writeLoopOnConverters(event);
}

protected String writeLoopOnConverters(E event) {
  StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE);
  Converter<E> c = head;
  while (c != null) {
    c.write(strBuilder, event);
    c = c.getNext();
  }
  return strBuilder.toString();
}

head 是如何赋值的

微信公众号:CoderLi

微信公众号:CoderLi

微信公众号:CoderLi

解释 pattern 表达式

%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n

微信公众号:CoderLi

Converter 主要主要的作用就是解释表达式对应的字符串。可以借助这个 converter 做日志脱敏

微信公众号:CoderLi

本文由mdnice多平台发布

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值