日志适配
本文基于 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 构造方法
```plain text plain /** * 已创建的 Logger 对应的映射 * * key:类名 */ private static final ConcurrentMap<String, FailsafeLogger> LOGGERS = new ConcurrentHashMap<String, FailsafeLogger>(); /** * 当前使用的 LoggerAdapter 日志适配器 */ private static volatile LoggerAdapter LOGGER_ADAPTER; private LoggerFactory() {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
- 通过**设置**
的
LOGGER_ADAPTER
,创建对应的
com.alibaba.dubbo.common.logger.Logger
实现类,添加到
LOGGERS
中。下文,我们会详细解析。
## 2.2 setLoggerAdapter
#setLoggerAdapter(String loggerAdapter)**静态**方法,设置 LoggerAdapter ,代码如下:
```plain text
plain public static void setLoggerAdapter(String loggerAdapter) { if (loggerAdapter != null && loggerAdapter.length() > 0) { setLoggerAdapter(ExtensionLoader.getExtensionLoader(LoggerAdapter.class).getExtension(loggerAdapter)); } }
- 根据拓展名对应的 ( 注意,不是类名 ),获得 LoggerAdapter 实现类。并调用 #setLoggerAdapter(LoggerAdapter) 方法,设置 LoggerAdapter 对象。代码如下:
```plain text plain 1: public static void setLoggerAdapter(LoggerAdapter loggerAdapter) { 2: if (loggerAdapter != null) { 3: // 获得 Logger 对象,并打印日志,提示设置后的 LoggerAdapter 实现类 4: Logger logger = loggerAdapter.getLogger(LoggerFactory.class.getName()); 5: logger.info(“using logger: “ + loggerAdapter.getClass().getName()); 6: // 设置 LOGGER_ADAPTER 属性 7: LoggerFactory.LOGGER_ADAPTER = loggerAdapter; 8: // 循环,将原有已经生成的 LOGGER 缓存对象,全部重新生成替换 9: for (Map.Entry<String, FailsafeLogger> entry : LOGGERS.entrySet()) { 10: entry.getValue().setLogger(LOGGER_ADAPTER.getLogger(entry.getKey())); 11: } 12: } 13: }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
- 第 3 至 5 行:调用 **对应设置后**[「3. LoggerAdapter」](http://svip.iocoder.cn/Dubbo/logger-adaptive/#)
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 属性。代码如下:
```plain text
plain 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 属性。 代码如下:
```plain text plain private String logger; public void setLogger(String logger) { this.logger = logger; // 设置 LoggerAdapter LoggerFactory.setLoggerAdapter(logger); }
1
2
3
4
5
6
7
8
---
- 通过如下方式配置时,都会调用该方法:
1. 命令行
```plain text
plain java -Ddubbo.application.logger=log4j
- 在 dubbo.properties 中指定
```plain text plain dubbo.application.logger=log4j
1
2
3
4
5
6
7
8
9
---
3. 在
dubbo.xml
中配置
```plain text
plain <dubbo:application logger="log4j" />
2.3 getLogger
#getLogger(…) 方法,优先从 LOGGERS 中,获得对应的 Logger 对象。若不存在,则进行创建,并进行缓存到 LOGGERS 中。代码如下:
```plain text plain 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; }
1
2
3
4
5
6
7
8
9
10
11
12
---
- FailsafeLogger ,我们在 [「4.1 FailsafeLogger」](http://svip.iocoder.cn/Dubbo/logger-adaptive/#)
中详细解析。
## 2.4 setLevel
#setLevel(Level level) 方法,设置日志级别。代码如下:
```plain text
plain public static void setLevel(Level level) { LOGGER_ADAPTER.setLevel(level); }
2.5 getLevel
#getLevel() 方法,获得日志级别。代码如下:
```plain text plain public static Level getLevel() { return LOGGER_ADAPTER.getLevel(); }
1
2
3
4
5
6
7
8
9
---
## 2.6 getFile
#getFile() 方法,获得**当前**日志文件。代码如下:
```plain text
plain public static File getFile() { return LOGGER_ADAPTER.getFile(); }
3. LoggerAdapter
com.alibaba.dubbo.common.logger.LoggerAdapter ,Logger 适配器接口,负责对接不同日志库的 LoggerFactory 。接口方法如下:
```plain text plain Logger getLogger(Class<?> key); Logger getLogger(String key); Level getLevel(); void setLevel(Level level); File getFile(); void setFile(File file);
1
2
3
4
5
6
7
8
9
10
11
---
## 3.1 Log4jLoggerAdapter
com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter ,实现 LoggerAdapter 接口,**log4j** 的 LoggerAdapter 实现类。
### 3.1.1 构造方法
```plain text
plain /** * 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) { } }
- file构造方法 属性,通过 ,进行初始化。
3.1.2 getLogger
```plain text plain @Override public Logger getLogger(Class<?> key) { return new Log4jLogger(LogManager.getLogger(key)); } @Override public Logger getLogger(String key) { return new Log4jLogger(LogManager.getLogger(key)); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
- 先调用
org.apache.log4j.LogManager#getLogger(…)
方法,获得
org.apache.log4j.Log
对象。
- 再将
org.apache.log4j.Log
对象作为方法参数,创建
com.alibaba.dubbo.common.logger.log4j.Log4jLogger
对象。
### 3.1.3 getLevel
```plain text
plain @Override public Level getLevel() { return fromLog4jLevel(LogManager.getRootLogger().getLevel()); }
- 获得 Root Logger 的日志级别。
- 调用 #fromLog4jLevel(Level) ,将 Log4j 的日志级别转成 Dubbo 的日志级别。代码如下:
```plain text plain 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; }
1
2
3
4
5
6
7
---
### 3.1.4 setLevel
```plain text
plain @Override public void setLevel(Level level) { LogManager.getRootLogger().setLevel(toLog4jLevel(level)); }
- 设置 Root Logger 的日志级别。
- 调用 #toLog4jLevel(Level) ,将 Dubbo 的日志级别转成 Log4j 的日志级别。代码如下:
```plain text plain 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; }
1
2
3
4
5
6
7
---
### 3.1.5 getFile
```plain text
plain @Override public File getFile() { return file; }
3.1.6 setFile
```plain text plain @Override public void setFile(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
---
- 不支持设置。
## 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
由
决定。代码如下:
```plain text
plain 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; } }
```plain text
- 也因此,打印日志的调用栈为:
Dubbo Slf4jLogger => Slf4j Logger => 真正的 Logger 实现类。 ```
3.4 JclLoggerAdapter
类似 Slf4jLoggerAdapter ,省略。
4. Logger
com.alibaba.dubbo.common.logger.Logger ,Logger 接口。代码如下:
```plain text plain 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();
1
2
3
4
5
6
7
8
9
10
11
12
13
---
- 方法的定义,复制自 Apache Common Logger 。
## 4.1 FailsafeLogger
com.alibaba.dubbo.common.logger.support.FailsafeLogger ,实现 Logger 接口,**失败安全**的 Logger 实现类。
我们以 #error(String msg)**实现**方法,举例子。代码如下:
```plain text
plain /** * 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 的 到日志中。代码如下:
```plain text plain private String appendContextMessage(String msg) { return “ [DUBBO] “ + msg + “, dubbo version: “ + Version.getVersion() + “, current host: “ + NetUtils.getLogHost(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
---
- 其他实现方法,也类似该方法。
## 4.2 Log4jLogger
com.alibaba.dubbo.common.logger.log4j.Log4jLogger ,实现 Logger 接口,**log4j** 的 Logger 实现类。
我们以 #error(String msg)**实现**方法,举例子。代码如下:
```plain text
plain 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 ,日志级别枚举。代码如下:
plain text plain public enum Level { ALL, TRACE, DEBUG, INFO, WARN, ERROR, OFF }
666. 彩蛋
一般情况下,我们使用 slf4j ,并使用 slf4j 对应的适配库。具体可参考:《dubbo应用配置logback日志》 。
