本文基于 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 构造方法
/** |
- 通过设置的
LOGGER_ADAPTER
,创建对应的com.alibaba.dubbo.common.logger.Logger
实现类,添加到LOGGERS
中。下文,我们会详细解析。
2.2 setLoggerAdapter
#setLoggerAdapter(String loggerAdapter)
静态方法,设置 LoggerAdapter ,代码如下:
public static void setLoggerAdapter(String loggerAdapter) { |
- 根据拓展名( 注意,不是类名 ),获得对应的 LoggerAdapter 实现类。并调用
#setLoggerAdapter(LoggerAdapter)
方法,设置 LoggerAdapter 对象。代码如下:
1: public static void setLoggerAdapter(LoggerAdapter loggerAdapter) { |
- 第 3 至 5 行:调用
LoggerAdapter#getLogger(String key)
方法,获得 LoggerFactory 的 对应 Logger 实现对象。然后,打印日志,提示设置后的 LoggerAdapter 实现类名。LoggerAdapter 相关,在 「3. LoggerAdapter」 详细解析。 - 第 7 行:设置
LOGGER_ADAPTER
属性。 - 第 8 至 11 行:循环
LOGGERS
,调用LoggerAdapter#getLogger(String key)
方法,重新生成 LOGGER ,进行替换。
2.2.1 静态代码块
在 LoggerFactory 的静态代码块,会根据 "logger"
配置项,调用 #setLoggerAdapter(LoggerAdapter)
方法,进行设置 LOGGER_ADAPTER
属性。代码如下:
static { |
- 🙂 代码易懂,看注释。
- 该方法适用于 LoggerFactory 未加载时,调用
System#setProperty("dubbo.application.logger", logger)
的初始化。
2.2.2 ApplicationConfig
ApplicationConfig 里,有 #setLogger(String logger)
方法,调用 #setLoggerAdapter(LoggerAdapter)
方法,进行设置 LOGGER_ADAPTER
属性。 代码如下:
private String logger; |
- 通过如下方式配置时,都会调用该方法:
命令行
java -Ddubbo.application.logger=log4j
在
dubbo.properties
中指定dubbo.application.logger=log4j
在
dubbo.xml
中配置<dubbo:application logger="log4j" />
2.3 getLogger
#getLogger(...)
方法,优先从 LOGGERS
中,获得对应的 Logger 对象。若不存在,则进行创建,并进行缓存到 LOGGERS
中。代码如下:
public static Logger getLogger(Class<?> key) { |
- FailsafeLogger ,我们在 「4.1 FailsafeLogger」 中详细解析。
2.4 setLevel
#setLevel(Level level)
方法,设置日志级别。代码如下:
public static void setLevel(Level level) { |
2.5 getLevel
#getLevel()
方法,获得日志级别。代码如下:
public static Level getLevel() { |
2.6 getFile
#getFile()
方法,获得当前日志文件。代码如下:
public static File getFile() { |
3. LoggerAdapter
com.alibaba.dubbo.common.logger.LoggerAdapter
,Logger 适配器接口,负责对接不同日志库的 LoggerFactory 。接口方法如下:
Logger getLogger(Class<?> key); |
3.1 Log4jLoggerAdapter
com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter
,实现 LoggerAdapter 接口,log4j 的 LoggerAdapter 实现类。
3.1.1 构造方法
/** |
file
属性,通过构造方法,进行初始化。
3.1.2 getLogger
|
- 先调用
org.apache.log4j.LogManager#getLogger(...)
方法,获得org.apache.log4j.Log
对象。 - 再将
org.apache.log4j.Log
对象作为方法参数,创建com.alibaba.dubbo.common.logger.log4j.Log4jLogger
对象。
3.1.3 getLevel
|
- 获得 Root Logger 的日志级别。
调用
#fromLog4jLevel(Level)
,将 Log4j 的日志级别转成 Dubbo 的日志级别。代码如下: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
|
- 设置 Root Logger 的日志级别。
调用
#toLog4jLevel(Level)
,将 Dubbo 的日志级别转成 Log4j 的日志级别。代码如下: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
|
3.1.6 setFile
|
- 不支持设置。
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
由真正的日志实现决定。代码如下:public class Slf4jLoggerAdapter implements LoggerAdapter {
private Level level;
private File file;
public Logger getLogger(String key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
public Logger getLogger(Class<?> key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
public Level getLevel() { // 无用
return level;
}
public void setLevel(Level level) { // 无用
this.level = level;
}
public File getFile() { // 无用
return file;
}
public void setFile(File file) { // 无用
this.file = file;
}
}- 也因此,打印日志的调用栈为:Dubbo Slf4jLogger => Slf4j Logger => 真正的 Logger 实现类。
3.4 JclLoggerAdapter
类似 Slf4jLoggerAdapter ,省略。
4. Logger
com.alibaba.dubbo.common.logger.Logger
,Logger 接口。代码如下:
void trace(String msg); |
- 方法的定义,复制自 Apache Common Logger 。
4.1 FailsafeLogger
com.alibaba.dubbo.common.logger.support.FailsafeLogger
,实现 Logger 接口,失败安全的 Logger 实现类。
我们以 #error(String msg)
实现方法,举例子。代码如下:
/** |
- 即使报错,也会被 try catch 掉。
#appendContextMessage(msg)
方法,拼接 Dubbo 的 version 和 host 到日志中。代码如下: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)
实现方法,举例子。代码如下:
private static final String FQCN = FailsafeLogger.class.getName(); |
- 每个实现方法,调用 Log4J Logger 对象,对应的方法。
- 其他实现方法,也类似该方法。
4.3 JdkLogger
类似 Log4jLogger ,省略。
4.4 Slf4jLogger
类似 Log4jLogger ,省略。
4.5 JclLogger
类似 Log4jLogger ,省略。
5. Level
com.alibaba.dubbo.common.logger.Level
,日志级别枚举。代码如下:
public enum Level { |
666. 彩蛋
一般情况下,我们使用 slf4j ,并使用 slf4j 对应的适配库。具体可参考:《dubbo应用配置logback日志》 。