三大组件
Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。
Logger
类作为 logback-classic 模块的一部分。Appender
与 Layouts
接口作为 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;
}
第二步:Logger 级别过滤
如果返回的 Reply 是 neutral 则会根据级别进行过滤。如果是 deny 则直接返回
第三步:创建 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);
}
从参数数组中提取异常
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;
}
这里有两个实现累、一个是同步的、这个正常是不会使用这个的了
我们直接看另一个的实现
/**
* 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;
}
这个 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);
}
ch.qos.logback.core.encoder.LayoutWrappingEncoder
public byte[] encode(E event) {
String txt = layout.doLayout(event);
return convertToBytes(txt);
}
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
是如何赋值的
解释 pattern 表达式
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n
Converter 主要主要的作用就是解释表达式对应的字符串。可以借助这个 converter 做日志脱敏
本文由 mdnice 多平台发布