本文基于 Dubbo 2.6.1 版本,望知悉。
1. 概述
本文分享 Dubbo 的日志适配,对应文档为:
自 2.2.1 开始,dubbo 开始内置 log4j、slf4j、jcl、jdk 这些日志框架的适配 1
整体类图如下:
2. LoggerFactory
com.alibaba.dubbo.common.logger.LoggerFactory ,是 com.alibaba.dubbo.common.logger.Logger 的工厂类。
2.1 构造方法
1
2
3
4
5
6
7
8
9
10
11
12
| /**
* 已创建的 Logger 对应的映射
*
* key:类名
*/
private static final ConcurrentMap<String, FailsafeLogger> LOGGERS = new ConcurrentHashMap<String, FailsafeLogger>();
/**
* 当前使用的 LoggerAdapter 日志适配器
*/
private static volatile LoggerAdapter LOGGER_ADAPTER;
private LoggerFactory() {}
|
- 通过设置 的 LOGGER_ADAPTER ,创建对应的 com.alibaba.dubbo.common.logger.Logger 实现类,添加到 LOGGERS 中。下文,我们会详细解析。
2.2 setLoggerAdapter
#setLoggerAdapter(String loggerAdapter)静态方法,设置 LoggerAdapter ,代码如下:
1
2
3
4
5
| public static void setLoggerAdapter(String loggerAdapter) {
if (loggerAdapter != null && loggerAdapter.length() > 0) {
setLoggerAdapter(ExtensionLoader.getExtensionLoader(LoggerAdapter.class).getExtension(loggerAdapter));
}
}
|
- 根据拓展名对应的 ( 注意,不是类名 ),获得 LoggerAdapter 实现类。并调用 #setLoggerAdapter(LoggerAdapter) 方法,设置 LoggerAdapter 对象。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
| public static void setLoggerAdapter(LoggerAdapter loggerAdapter) {
if (loggerAdapter != null) {
// 获得 Logger 对象,并打印日志,提示设置后的 LoggerAdapter 实现类
Logger logger = loggerAdapter.getLogger(LoggerFactory.class.getName());
logger.info("using logger: " + loggerAdapter.getClass().getName());
// 设置 LOGGER_ADAPTER 属性
LoggerFactory.LOGGER_ADAPTER = loggerAdapter;
// 循环,将原有已经生成的 LOGGER 缓存对象,全部重新生成替换
for (Map.Entry<String, FailsafeLogger> entry : LOGGERS.entrySet()) {
entry.getValue().setLogger(LOGGER_ADAPTER.getLogger(entry.getKey()));
}
}
}
|
- 第 3 至 5 行:调用 对应设置后「3. LoggerAdapter」 LoggerAdapter#getLogger(String key) 方法,获得 LoggerFactory 的 Logger 实现对象。然后,打印日志,提示 的 LoggerAdapter 实现类名。LoggerAdapter 相关,在 详细解析。
- 第 7 行:设置 LOGGER_ADAPTER 属性。
- 第 8 至 11 行:循环 LOGGERS ,调用 LoggerAdapter#getLogger(String key) 方法,重新生成 LOGGER ,进行替换。
2.2.1 静态代码块
在 LoggerFactory 的静态代码块,会根据 “logger” 配置项,调用 #setLoggerAdapter(LoggerAdapter) 方法,进行设置 LOGGER_ADAPTER 属性。代码如下:
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
| static {
// 获得 "logger" 配置项
String logger = System.getProperty("dubbo.application.logger");
// 根据配置项,进行对应的 LoggerAdapter 对象
if ("slf4j".equals(logger)) {
setLoggerAdapter(new Slf4jLoggerAdapter());
} else if ("jcl".equals(logger)) {
setLoggerAdapter(new JclLoggerAdapter());
} else if ("log4j".equals(logger)) {
setLoggerAdapter(new Log4jLoggerAdapter());
} else if ("jdk".equals(logger)) {
setLoggerAdapter(new JdkLoggerAdapter());
} else {
// 未配置,按照 log4j > slf4j > apache common logger > jdk logger
try {
setLoggerAdapter(new Log4jLoggerAdapter());
} catch (Throwable e1) {
try {
setLoggerAdapter(new Slf4jLoggerAdapter());
} catch (Throwable e2) {
try {
setLoggerAdapter(new JclLoggerAdapter());
} catch (Throwable e3) {
setLoggerAdapter(new JdkLoggerAdapter());
}
}
}
}
}
|
- 代码易懂,看注释。
- 该方法适用于 LoggerFactory 未加载时,调用 System#setProperty(“dubbo.application.logger”, logger) 的初始化。
2.2.2 ApplicationConfig
ApplicationConfig 里,有 #setLogger(String logger) 方法,调用 #setLoggerAdapter(LoggerAdapter) 方法,进行设置 LOGGER_ADAPTER 属性。 代码如下:
1
2
3
4
5
6
7
| private String logger;
public void setLogger(String logger) {
this.logger = logger;
// 设置 LoggerAdapter
LoggerFactory.setLoggerAdapter(logger);
}
|
1
| java -Ddubbo.application.logger=log4j
|
- 在 dubbo.properties 中指定
1
| dubbo.application.logger=log4j
|
- 在 dubbo.xml 中配置
1
| <dubbo:application logger="log4j" />
|
2.3 getLogger
#getLogger(…) 方法,优先从 LOGGERS 中,获得对应的 Logger 对象。若不存在,则进行创建,并进行缓存到 LOGGERS 中。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public static Logger getLogger(Class<?> key) {
// 从缓存中,获得 Logger 对象
FailsafeLogger logger = LOGGERS.get(key.getName());
// 不存在,则进行创建,并进行缓存
if (logger == null) {
LOGGERS.putIfAbsent(key.getName(), new FailsafeLogger(LOGGER_ADAPTER.getLogger(key)));
logger = LOGGERS.get(key.getName());
}
return logger;
}
public static Logger getLogger(String key) {
// 从缓存中,获得 Logger 对象
FailsafeLogger logger = LOGGERS.get(key);
// 不存在,则进行创建,并进行缓存
if (logger == null) {
LOGGERS.putIfAbsent(key, new FailsafeLogger(LOGGER_ADAPTER.getLogger(key)));
logger = LOGGERS.get(key);
}
return logger;
}
|
2.4 setLevel
#setLevel(Level level) 方法,设置日志级别。代码如下:
1
2
3
| public static void setLevel(Level level) {
LOGGER_ADAPTER.setLevel(level);
}
|
2.5 getLevel
#getLevel() 方法,获得日志级别。代码如下:
1
2
3
| public static Level getLevel() {
return LOGGER_ADAPTER.getLevel();
}
|
2.6 getFile
#getFile() 方法,获得当前日志文件。代码如下:
1
2
3
| public static File getFile() {
return LOGGER_ADAPTER.getFile();
}
|
3. LoggerAdapter
com.alibaba.dubbo.common.logger.LoggerAdapter ,Logger 适配器接口,负责对接不同日志库的 LoggerFactory 。接口方法如下:
1
2
3
4
5
6
7
8
| Logger getLogger(Class<?> key);
Logger getLogger(String key);
Level getLevel();
void setLevel(Level level);
File getFile();
void setFile(File file);
|
3.1 Log4jLoggerAdapter
com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter ,实现 LoggerAdapter 接口,log4j 的 LoggerAdapter 实现类。
3.1.1 构造方法
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
| /**
* Root Logger 的文件,在构造方法中初始化
*/
private File file;
@SuppressWarnings("unchecked")
public Log4jLoggerAdapter() {
try {
// 获得 Root Logger 对象
org.apache.log4j.Logger logger = LogManager.getRootLogger();
if (logger != null) {
// 循环每个 Logger 对象的 Appender 对象
Enumeration<Appender> appenders = logger.getAllAppenders();
if (appenders != null) {
while (appenders.hasMoreElements()) {
// 当且仅当 FileAppender 时
Appender appender = appenders.nextElement();
if (appender instanceof FileAppender) {
FileAppender fileAppender = (FileAppender) appender;
String filename = fileAppender.getFile();
file = new File(filename);
break;
}
}
}
}
} catch (Throwable t) {
}
}
|
3.1.2 getLogger
1
2
3
4
5
6
7
8
9
| @Override
public Logger getLogger(Class<?> key) {
return new Log4jLogger(LogManager.getLogger(key));
}
@Override
public Logger getLogger(String key) {
return new Log4jLogger(LogManager.getLogger(key));
}
|
- 先调用 org.apache.log4j.LogManager#getLogger(…) 方法,获得 org.apache.log4j.Log 对象。
- 再将 org.apache.log4j.Log 对象作为方法参数,创建 com.alibaba.dubbo.common.logger.log4j.Log4jLogger 对象。
3.1.3 getLevel
1
2
3
4
| @Override
public Level getLevel() {
return fromLog4jLevel(LogManager.getRootLogger().getLevel());
}
|
- 获得 Root Logger 的日志级别。
- 调用 #fromLog4jLevel(Level) ,将 Log4j 的日志级别转成 Dubbo 的日志级别。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| private static Level fromLog4jLevel(org.apache.log4j.Level level) {
if (level == org.apache.log4j.Level.ALL)
return Level.ALL;
if (level == org.apache.log4j.Level.TRACE)
return Level.TRACE;
if (level == org.apache.log4j.Level.DEBUG)
return Level.DEBUG;
if (level == org.apache.log4j.Level.INFO)
return Level.INFO;
if (level == org.apache.log4j.Level.WARN)
return Level.WARN;
if (level == org.apache.log4j.Level.ERROR)
return Level.ERROR;
// if (level == org.apache.log4j.Level.OFF)
return Level.OFF;
}
|
3.1.4 setLevel
1
2
3
4
| @Override
public void setLevel(Level level) {
LogManager.getRootLogger().setLevel(toLog4jLevel(level));
}
|
- 设置 Root Logger 的日志级别。
- 调用 #toLog4jLevel(Level) ,将 Dubbo 的日志级别转成 Log4j 的日志级别。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| private static org.apache.log4j.Level toLog4jLevel(Level level) {
if (level == Level.ALL)
return org.apache.log4j.Level.ALL;
if (level == Level.TRACE)
return org.apache.log4j.Level.TRACE;
if (level == Level.DEBUG)
return org.apache.log4j.Level.DEBUG;
if (level == Level.INFO)
return org.apache.log4j.Level.INFO;
if (level == Level.WARN)
return org.apache.log4j.Level.WARN;
if (level == Level.ERROR)
return org.apache.log4j.Level.ERROR;
// if (level == Level.OFF)
return org.apache.log4j.Level.OFF;
}
|
3.1.5 getFile
1
2
3
4
| @Override
public File getFile() {
return file;
}
|
3.1.6 setFile
1
2
| @Override
public void setFile(File file) { }
|
3.2 JdkLoggerAdapter
类似 Log4jLoggerAdapter ,省略。
3.3 Slf4jLoggerAdapter
com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter ,实现 LoggerAdapter 接口,slf4j 的 LoggerAdapter 实现类。
SLF4J 不同于其他日志类库,与其它日志类库有很大的不同。SLF4J (Simple logging Facade for Java) 不是一个真正的日志实现,而是一个抽象层( abstraction layer),它允许你在后台使用任意一个日志类库。如果是在编写供内外部都可以使用的API或者通用类库,那么你真不会希望使用你类库的客户端必须使用你选择的日志类库。
- 因此,Slf4jLoggerAdapter 的实现方法中,操作 无用真正的日志实现 file 和 level 属性是 。因为,具体的 file 和 level 由 决定。代码如下:
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
| public class Slf4jLoggerAdapter implements LoggerAdapter {
private Level level;
private File file;
@Override
public Logger getLogger(String key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
@Override
public Logger getLogger(Class<?> key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
@Override
public Level getLevel() { // 无用
return level;
}
@Override
public void setLevel(Level level) { // 无用
this.level = level;
}
@Override
public File getFile() { // 无用
return file;
}
@Override
public void setFile(File file) { // 无用
this.file = file;
}
}
|
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
| - 也因此,打印日志的调用栈为:</font>**Dubbo</font>** Slf4jLogger => </font>**Slf4j</font>** Logger => </font>**真正</font>**的 Logger 实现类。```
## 3.4 JclLoggerAdapter
类似 Slf4jLoggerAdapter ,省略。
# 4. Logger
com.alibaba.dubbo.common.logger.Logger ,Logger 接口。代码如下:
```java
void trace(String msg);
void trace(Throwable e);
void trace(String msg, Throwable e);
void debug(String msg);
void debug(Throwable e);
void debug(String msg, Throwable e);
void info(String msg);
void info(Throwable e);
void info(String msg, Throwable e);
void warn(String msg);
void warn(Throwable e);
void warn(String msg, Throwable e);
void error(String msg);
void error(Throwable e);
void error(String msg, Throwable e);
boolean isTraceEnabled();
boolean isDebugEnabled();
boolean isInfoEnabled();
boolean isWarnEnabled();
boolean isErrorEnabled();
|
- 方法的定义,复制自 Apache Common Logger 。
4.1 FailsafeLogger
com.alibaba.dubbo.common.logger.support.FailsafeLogger ,实现 Logger 接口,失败安全的 Logger 实现类。
我们以 #error(String msg)实现方法,举例子。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| /**
* Dubbo Logger 对象
*/
private Logger logger;
public FailsafeLogger(Logger logger) {
this.logger = logger;
}
@Override
public void error(String msg) {
try {
logger.error(appendContextMessage(msg));
} catch (Throwable t) {
}
}
|
- 即使报错,也会被 try catch 掉。
- #appendContextMessage(msg)version 和 host 方法,拼接 Dubbo 的 到日志中。代码如下:
1
2
3
| private String appendContextMessage(String msg) {
return " [DUBBO] " + msg + ", dubbo version: " + Version.getVersion() + ", current host: " + NetUtils.getLogHost();
}
|
4.2 Log4jLogger
com.alibaba.dubbo.common.logger.log4j.Log4jLogger ,实现 Logger 接口,log4j 的 Logger 实现类。
我们以 #error(String msg)实现方法,举例子。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
| private static final String FQCN = FailsafeLogger.class.getName();
private final org.apache.log4j.Logger logger;
public Log4jLogger(org.apache.log4j.Logger logger) {
this.logger = logger;
}
@Override
public void error(String msg) {
logger.log(FQCN, Level.ERROR, msg, null);
}
|
- 每个实现方法,调用 Log4J Logger 对象,对应的方法。
- 其他实现方法,也类似该方法。
4.3 JdkLogger
类似 Log4jLogger ,省略。
4.4 Slf4jLogger
类似 Log4jLogger ,省略。
4.5 JclLogger
类似 Log4jLogger ,省略。
5. Level
com.alibaba.dubbo.common.logger.Level ,日志级别枚举。代码如下:
1
2
3
4
5
6
7
8
9
10
11
| public enum Level {
ALL,
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
OFF
}
|
666. 彩蛋
一般情况下,我们使用 slf4j ,并使用 slf4j 对应的适配库。具体可参考:《dubbo应用配置logback日志》 。