logback源码及原理分析

如何看源码及原理分析

原理及源码查看是相辅相成。只能了解原理才能更好的了解源码;只有相应的了解源码才能知道是怎么原理实现。

原理:

​ 自已设计一个。配合源码查看,验证框架设计者是怎么设计的。

源码:

​ 只要知道入口。使用时分几步。分别查看。

logback简单使用

加入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.11</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.11</version>
</dependency>
</dependencies>

使用:

1
2
3
4
5
6
7
8
public class Test {

public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Test.class);
logger.debug("Hello world.");
}

}

这时运行会在控制台打印日志。

程序在IO上会有标准输入、标准输出、错误输出。

配置文件

logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>


<root>
<appender-ref ref="FILE"/>
</root>
</configuration>

如果添加这个文件到资源目录下,会把日志写入myApp.log文件。

源码分析

从使用上来看。如果没有配置文件,框架会使用默认配置。有配置文件加载配置文件。根据提供的LoggerFactory类获取Logger,Logger调用方法打印日志。

LoggerFactory 看名字就是一个工厂类。又是一个入口类。这个类肯定会有默认配置和加配置文件的功能。生成Logger。

Logger是提供打印日志的方法。

加载配置文件源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Logger logger = LoggerFactory.getLogger(Test.class);
Logger logger = getLogger(clazz.getName());
ILoggerFactory iLoggerFactory = getILoggerFactory();
performInitialization();
bind();
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); #这时会找到org/slf4j/impl/StaticLoggerBinder.class

StaticLoggerBinder.getSingleton();
SINGLETON.init();
new ContextInitializer(defaultLoggerContext).autoConfig(); # 看到autoConfig了应该就到目标了。

URL url = findURLOfDefaultConfigurationFile(true);

# 看系统参数logback.configurationFile 是否有设置
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);

# logback.groovy 配置文件
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
# logback-test.xml 配置文件
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
# logback.xml 配置文件
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);

最后url 是null时说明没有配置文件

BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
#进入configure方法
这里配置了控制台输出的Appender、Encoder、Layout

这时就有Appender、Encoder、Layout要知道是啥

Appender是输出到哪去。

Encoder是输出的编码。

Layout是打印日志的格式。

Logger上下文

1
2
3
contextSelectorBinder.getContextSelector()
return contextSelectorBinder.getContextSelector().getLoggerContext();
ch.qos.logback.classic.LoggerContext

Logger

1
2
3
4
5
return iLoggerFactory.getLogger(name);
LoggerContext类
childLogger = logger.createChildByName(childName);
childLogger = new Logger(childName, this, this.loggerContext);
loggerCache.put(childName, childLogger);

logger的结构为: 比如有一个com.test.a的类,这时会生成如下logger的父子结构

1
2
3
4
root
com
com.test
com.test.a

打印日志源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
logger.debug("Hello world.");
filterAndLog_0_Or3Plus(FQCN, null, Level.DEBUG, msg, null, null);
buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
callAppenders(le);
writes += l.appendLoopOnAppenders(event);
return aai.appendLoopOnAppenders(event);
final Appender<E>[] appenderArray = appenderList.asTypedArray();
appenderArray[i].doAppend(e);
# 这时会调用具体的Appender的实现类
this.append(eventObject);
UnsynchronizedAppenderBase.doAppend(E eventObject)
this.append(eventObject);
OutputStreamAppender.subAppend(E event)
lock.lock();
try {
writeOut(event);
} finally {
lock.unlock();
}
this.encoder.doEncode(event);
String txt = layout.doLayout(event);
outputStream.write(convertToBytes(txt));
if (immediateFlush)
outputStream.flush();

System.out.write(b);

这时发现consoleAppender使用ReentrantLock, system.out.write。

结构

Logger

1
ch.qos.logback.classic.Logger

Appender 在root的logger中AppenderAttachableImpl aai

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Appender (ch.qos.logback.core)
UnsynchronizedAppenderBase (ch.qos.logback.core)
AsyncAppenderBase (ch.qos.logback.core)
#异步写日志
AsyncAppender (ch.qos.logback.classic)
OutputStreamAppender (ch.qos.logback.core)
# 同步写控制台
ConsoleAppender (ch.qos.logback.core)
# 同步写文件
FileAppender (ch.qos.logback.core)
# 滚动同步写文件
RollingFileAppender (ch.qos.logback.core.rolling)
DBAppenderBase (ch.qos.logback.core.db)
DBAppender (ch.qos.logback.classic.db)
AppenderBase (ch.qos.logback.core)
NOPAppender (ch.qos.logback.core.helpers)
AbstractServerSocketAppender (ch.qos.logback.core.net.server)
SSLServerSocketAppenderBase (ch.qos.logback.core.net.server)
SSLServerSocketAppender (ch.qos.logback.classic.net.server)
ServerSocketAppender (ch.qos.logback.classic.net.server)
JMSAppenderBase (ch.qos.logback.core.net)
JMSTopicAppender (ch.qos.logback.classic.net)
JMSQueueAppender (ch.qos.logback.classic.net)
SyslogAppenderBase (ch.qos.logback.core.net)
SyslogAppender (ch.qos.logback.classic.net)
SMTPAppenderBase (ch.qos.logback.core.net)
SMTPAppender (ch.qos.logback.classic.net)
SiftingAppenderBase (ch.qos.logback.core.sift)
SiftingAppender (ch.qos.logback.classic.sift)
CyclicBufferAppender (ch.qos.logback.core.read)
ListAppender (ch.qos.logback.core.read)
AbstractSocketAppender (ch.qos.logback.core.net)
AbstractSSLSocketAppender (ch.qos.logback.core.net)
SSLSocketAppender (ch.qos.logback.classic.net)
SocketAppender (ch.qos.logback.classic.net)

常用的 ConsoleAppender、FileAppender、RollingFileAppender、AsyncAppender

Encoder

1
2
3
4
5
6
7
8
9
10
Encoder (ch.qos.logback.core.encoder)
EncoderBase (ch.qos.logback.core.encoder)
# 输出不变只加换行符
EchoEncoder (ch.qos.logback.core.encoder)
# 布局包装
LayoutWrappingEncoder (ch.qos.logback.core.encoder)
PatternLayoutEncoderBase (ch.qos.logback.core.pattern)
# 图案布局
PatternLayoutEncoder (ch.qos.logback.classic.encoder)
ObjectStreamEncoder (ch.qos.logback.core.encoder)

一般使用LayoutWrappingEncoder、PatternLayoutEncoder

Layout

1
2
3
4
5
6
7
8
9
10
11
12
13
Layout (ch.qos.logback.core)
LayoutBase (ch.qos.logback.core)
# xml布局
XMLLayout (ch.qos.logback.classic.log4j)
# 固定格式的布局 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
TTLLLayout (ch.qos.logback.classic.layout)
HTMLLayoutBase (ch.qos.logback.core.html)
# html布局
HTMLLayout (ch.qos.logback.classic.html)
EchoLayout (ch.qos.logback.core.layout)
PatternLayoutBase (ch.qos.logback.core.pattern)
# 使用模式字符串配置的灵活布局。
PatternLayout (ch.qos.logback.classic)

格式

1
2
3
4
5
6
7
8
<configuration>
多个appender
<appender></appender>
多个logger
<logger></logger>
一个root
<root></root>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/Users/qichuanhan/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} -[%X{traceId}]- [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>

<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/my-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/my-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>

</configuration>

AsyncAppender

消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Worker extends Thread {

public void run() {
AsyncAppenderBase<E> parent = AsyncAppenderBase.this;
AppenderAttachableImpl<E> aai = parent.aai;

// loop while the parent is started
while (parent.isStarted()) {
try {
E e = parent.blockingQueue.take();
aai.appendLoopOnAppenders(e);
} catch (InterruptedException ie) {
break;
}
}

addInfo("Worker thread will flush remaining events before exiting. ");

for (E e : parent.blockingQueue) {
aai.appendLoopOnAppenders(e);
parent.blockingQueue.remove(e);
}

aai.detachAndStopAllAppenders();
}
}
1
2
BlockingQueue<E> blockingQueue;
public static final int DEFAULT_QUEUE_SIZE = 256;

生产者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
UnsynchronizedAppenderBase.doAppend(E eventObject);
this.append(eventObject);
@Override
protected void append(E eventObject) {
if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) {
return;
}
preprocess(eventObject);
put(eventObject);
}
private void put(E eventObject) {
if (neverBlock) {
blockingQueue.offer(eventObject);
} else {
try {
blockingQueue.put(eventObject);
} catch (InterruptedException e) {
// Interruption of current thread when in doAppend method should not be consumed
// by AsyncAppender
Thread.currentThread().interrupt();
}
}
}

logback源码及原理分析
http://hanqichuan.com/2022/05/28/java/logback源码及原理分析/
作者
韩启川
发布于
2022年5月28日
许可协议