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