slfj
介绍
slfj 是apache 出品的一个日志打印规范的api 和一些简单的基本实现。 具体的实现jar包有log4j,logback。目前就实现逻辑上来说 logback 是对源码级的直接实现,log4j 是需要一个适配层来实现了slfj。就对这样的实现方式来说我们就logback 的源码逻辑来剖析他的原理
stop 1
目前呢我们也不知道他是怎么去处理这个逻辑的,那么最简单的方式就是看他们的使用文档从使用文档上来看我们第一步一般都是需要通过以下方式来实现
// 简单的log 实现
public class LogDemo {
private static Logger logger = LoggerFactory.getLogger(LogDemo.class);
public void print(String msg,Object ...arg){
logger.info(msg,arg);
}
public static void main(String[] args) {
LogDemo logDemo = new LogDemo();
logDemo.print("this is log {}","123");
}
}
对此我们还需要有一个log 的配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration >
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志输出编码 -->
<Encoding>UTF-8</Encoding>
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</layout>
</appender>
<root lever="info">
<!-- 指定默认的日志输出 -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
结果就可以打印出来了。
那好我们就现在的逻辑去分析这个日志有哪些点是我们要考察的
1.我们配置的logback.xml 文件指定了基本的打印
2.我们创建了一个Logger logger
这个类,通过它来打印出来了
2022-08-15 11:24:15.126 [main] INFO com.payermax.infra.adaptor.LogDemo - this is log 12
这一行日志。
3.所以思路就很明显了我们要从Logger logger
创建类作为入口来查看原理
step 2
# LoggerFactory这里初始化的过程
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE==UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE==UNINITIALIZED) {
INITIALIZATION_STATE=ONGOING_INITIALIZATION;
performInitialization();// 核心初始化
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION: //这里就是 slfj 对外提供的口子
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
returnNOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
returnSUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
核心就是对外提供个口子 StaticLoggerBinder 这个类
以包路径的方式org.slf4j.impl.StaticLoggerBinder
来实现
实现
logback 的实现
这里可以看到是通过 static 的方式来初始化的
从这里看到autoConfig就是查找我们指定的logback.xml 文件和去解析他。
提供了两个xml 和一个系统配置的方式
- resource 目录下的 logback.xml
- resource 目录下的logback-test.ml
- System.getProperty 的file 路径的方式
#ContextInitializer 提供了两个xml 和一个系统配置的方式
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";
log4j 的实现
stop 3
那么现在已经知道是如何加载配置了,接下来就是配置的logger 的构建方式了
Logger getLogger(final String name)
真实构建logger 的地方就是这里了。 入参是我们提供的某个类名。但是走到一下阶段后笔者是懵逼的,原因是:作为一个没看过使用文档的人来说应该创建一个logger 就可以了,但是这里创建了一些列的logger。由此我们可以想到他这种层级结构的方式有肯定是有地方可配置的。笔者查阅xml 配置后看到了 <logger>
这个标签就明白了。
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;
}
}
使用场景的话可以是按照项目的路径名来使用自己的appender,比如对自己项目日志脱敏等。
从sl4j 定义的info接口进入到logback真实的接口中最终都会走到这里
public void callAppenders(ILoggingEvent event) {
int writes = 0;
// 这里循环就是为了我们logger标签定义了不同路径的层级打印
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);
}
}
总结
对于以上的step 路程中。 我们要从简单的使用上去分析它的基本逻辑,我们从基本逻辑出发一点点剖析它的实现方式,从代码层次上再去反推它的使用方法。从而更能比我们直接通过配置去了解带来的印象更深刻,也能锻炼我们的思维能力。