1. 项目背景与核心挑战
去年参加大学生智能车竞赛时,我们团队遇到了一个极具挑战性的题目——"飞跃雷区"。这个项目要求智能车在高速行进中准确识别地面雷区标记,并通过机械结构实现飞跃动作。更棘手的是,比赛场地光照条件复杂多变,从强光直射到阴影交替出现,这对传统单目视觉方案提出了严峻考验。
经过多次实地测试,我们发现单一摄像头方案存在几个致命缺陷:首先是在逆光环境下雷区标记识别率骤降至60%以下;其次是高速运动导致的图像模糊严重影响测# 1. 概述
本文分享 日志初始化 的流程。在 《精尽 Dubbo 源码分析 —— 项目结构一览》「3.2 配置说明」 中,我们看到 Dubbo 配置了日志组件为 log4j 。那么 Dubbo 是如何初始化的呢?让我们一起来瞅瞅。
2. LoggerFactory
com.alibaba.dubbo.common.logger.LoggerFactory ,Logger 工厂类。
2.1 构造方法
Java复制/**
* 已创建的 Logger 对应的映射
*
* key :类名
*/
private static final ConcurrentMap<String, FailsafeLogger> LOGGERS = new ConcurrentHashMap<String, FailsafeLogger>();
/**
* 当前使用的日志组件
*/
private static volatile LoggerAdapter LOGGER_ADAPTER;
static {
// 获得 LoggerAdapter 对象
String logger = System.getProperty("dubbo.application.logger");
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 {
try {
setLoggerAdapter(new Log4jLoggerAdapter());
} catch (Throwable e1) {
try {
setLoggerAdapter(new Slf4jLoggerAdapter());
} catch (Throwable e2) {
try {
setLoggerAdapter(new JclLoggerAdapter());
} catch (Throwable e3) {
setLoggerAdapter(new JdkLoggerAdapter());
}
}
}
}
}
LOGGERS静态属性,已创建的 Logger 的映射。其中,FailsafeLogger 是 Logger 的装饰器,在 「2.3 FailsafeLogger」 详细解析。LOGGER_ADAPTER静态属性,当前使用的日志组件。我们可以看到,Dubbo 支持了多种日志组件,通过"dubbo.application.logger"配置,可设置使用的日志组件。默认情况下,按照 log4j => slf4j => jcl => jdk 的顺序尝试初始化对应的 LoggerAdapter 对象,直到成功。- LoggerAdapter 的实现类,如下:
- Log4jLoggerAdapter
- Slf4jLoggerAdapter
- JclLoggerAdapter
- JdkLoggerAdapter
2.2 getLogger
#getLogger(...) 静态方法,获得 Logger 对象。代码如下:
Java复制/**
* 获取日志输出器
*
* @param key 分类键
* @return 日志输出器, 后验条件: 不返回null.
*/
public static Logger getLogger(Class<?> key) {
return LOGGERS.computeIfAbsent(key.getName(), name -> new FailsafeLogger(LOGGER_ADAPTER.getLogger(name)));
}
/**
* 获取日志输出器
*
* @param key 分类键
* @return 日志输出器, 后验条件: 不返回null.
*/
public static Logger getLogger(String key) {
return LOGGERS.computeIfAbsent(key, k -> new FailsafeLogger(LOGGER_ADAPTER.getLogger(k)));
}
- 首先,从
LOGGERS中,获得 Logger 对象。若不存在,调用LoggerAdapter#getLogger(key)方法,创建对应的 Logger 对象,并包装成 FailsafeLogger 对象。
2.3 FailsafeLogger
com.alibaba.dubbo.common.logger.support.FailsafeLogger ,实现 Logger 接口,支持失败安全的 Logger 实现类。
失败安全,是指,在日志打印时,若出现异常,不将异常抛出,从而不影响业务逻辑。
2.3.1 构造方法
Java复制private Logger logger;
public FailsafeLogger(Logger logger) {
this.logger = logger;
}
2.3.2 实现方法
每个实现方法,都会调用 #appendContextMessage() 方法,拼接上下文信息。例如:
Java复制@Override
public void error(String msg, Throwable e) {
try {
logger.error(appendContextMessage(msg), e);
} catch (Throwable t) {
}
}
- 在
logger.error(...)方法外,包装了一层try...catch...代码块,实现失败安全。
#appendContextMessage() 方法,拼接上下文信息。代码如下:
Java复制private String appendContextMessage(String msg) {
// 追加当前线程名
StringBuilder buf = new StringBuilder();
buf.append(" [").append(Thread.currentThread().getName()).append("] ");
// 追加消息
buf.append(msg);
return buf.toString();
}
2.4 setLoggerAdapter
#setLoggerAdapter(LoggerAdapter) 静态方法,设置 LoggerAdapter 对象。代码如下:
Java复制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 对象,重新使用新的 LoggerAdapter 创建
for (Map.Entry<String, FailsafeLogger> entry : LOGGERS.entrySet()) {
entry.getValue().setLogger(LOGGER_ADAPTER.getLogger(entry.getKey()));
}
}
}
- 例如,调用了
System.setProperty("dubbo.application.logger", "slf4j");方法后,就会触发这个方法,进行 LoggerAdapter 的切换。
3. LoggerAdapter
com.alibaba.dubbo.common.logger.LoggerAdapter ,Logger 适配器接口。代码如下:
Java复制public interface LoggerAdapter {
/**
* 获取日志输出器
*
* @param key 分类键
* @return 日志输出器, 后验条件: 不返回null.
*/
Logger getLogger(Class<?> key);
/**
* 获取日志输出器
*
* @param key 分类键
* @return 日志输出器, 后验条件: 不返回null.
*/
Logger getLogger(String key);
/**
* 设置输出等级
*
* @param level 输出等级
*/
void setLevel(Level level);
/**
* 获取当前日志等级
*
* @return 当前日志等级
*/
Level getLevel();
/**
* 获取当前日志文件
*
* @return 当前日志文件
*/
File getFile();
/**
* 设置当前日志文件
*
* @param file 当前日志文件
*/
void setFile(File file);
}
3.1 Log4jLoggerAdapter
com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter ,实现 LoggerAdapter 接口,Log4j Logger 适配器实现类。
3.1.1 构造方法
Java复制/**
* 日志文件路径配置名
*/
private static final String FILE_KEY = "dubbo.log4j.file";
/**
* 日志级别配置名
*/
private static final String LEVEL_KEY = "dubbo.log4j.level";
/**
* 日志子目录径配置名
*/
private static final String SUB_DIRECTORY_KEY = "dubbo.log4j.subdirectory";
/**
* 日志文件
*/
private File file;
/**
* 日志级别
*/
private Level level;
public Log4jLoggerAdapter() {
try {
// 获得 log4j 的 Logger 对象
org.apache.log4j.Logger logger = org.apache.log4j.Logger.getRootLogger();
if (logger != null) {
// 循环每个 Logger 的 Appender 对象
Enumeration<Appender> appenders = logger.getAllAppenders();
if (appenders.hasMoreElements()) {
Appender appender = appenders.nextElement();
if (appender instanceof FileAppender) {
// 获得日志文件路径
String filename = ((FileAppender) appender).getFile();
file = new File(filename);
}
}
}
} catch (Throwable t) {
}
// 初始化 level 属性
String levelStr = System.getProperty(LEVEL_KEY);
if (levelStr != null && levelStr.length() > 0) {
level = Level.toLevel(levelStr);
} else {
level = Level.ALL;
}
// 初始化 file 属性
String filePath = System.getProperty(FILE_KEY);
if (filePath != null && filePath.length() > 0) {
file = new File(filePath);
// 若目录不存在,则创建
if (!file.exists()) {
file.getParentFile().mkdirs();
}
// 重置 log4j 的配置
reset();
}
}
- 代码比较易懂,看下注释。
3.1.2 getLogger
Java复制@Override
public Logger getLogger(String key) {
return new Log4jLogger(org.apache.log4j.Logger.getLogger(key));
}
@Override
public Logger getLogger(Class<?> key) {
return new Log4jLogger(org.apache.log4j.Logger.getLogger(key));
}
- 创建 Log4jLogger 对象。在 「3.1.4 Log4jLogger」 详细解析。
3.1.3 reset
#reset() 方法,重置 log4j 的配置。代码如下:
Java复制private void reset() {
try {
// 获得 Logger 对象
org.apache.log4j.Logger root = org.apache.log4j.Logger.getRootLogger();
// 设置日志级别
root.setLevel(level);
// 移除已有的 Appender 对象
if (root.getAllAppenders().hasMoreElements()) {
Enumeration<Appender> appenders = root.getAllAppenders();
while (appenders.hasMoreElements()) {
root.removeAppender(appenders.nextElement());
}
}
// 添加新的 Appender 对象
String subDirectory = System.getProperty(SUB_DIRECTORY_KEY);
String realLogfile = file.getAbsolutePath();
if (subDirectory != null && subDirectory.length() > 0) {
realLogfile = realLogfile + "." + subDirectory;
}
// 每日滚动日志文件
DailyRollingFileAppender rollingFileAppender = new DailyRollingFileAppender();
rollingFileAppender.setName("application");
rollingFileAppender.setLayout(new PatternLayout("%d [%t] %-5p %C{6} (%F:%L) - %m%n"));
rollingFileAppender.setFile(realLogfile);
rollingFileAppender.setEncoding("UTF-8");
rollingFileAppender.setDatePattern("'.'yyyy-MM-dd");
rollingFileAppender.activateOptions();
root.addAppender(rollingFileAppender);
} catch (Throwable t) {
throw new IllegalStateException("Failed to create logging system : " + t.getMessage(), t);
}
}
3.1.4 Log4jLogger
com.alibaba.dubbo.common.logger.log4j.Log4jLogger ,实现 Logger 接口,Log4j Logger 实现类。代码如下:
Java复制public class Log4jLogger implements Logger {
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 trace(String msg) {
logger.log(FQCN, Level.TRACE, msg, null);
}
@Override
public void trace(Throwable e) {
logger.log(FQCN, Level.TRACE, e == null ? null : e.getMessage(), e);
}
@Override
public void trace(String msg, Throwable e) {
logger.log(FQCN, Level.TRACE, msg, e);
}
@Override
public void debug(String msg) {
logger.log(FQCN, Level.DEBUG, msg, null);
}
@Override
public void debug(Throwable e) {
logger.log(FQCN, Level.DEBUG, e == null ? null : e.getMessage(), e);
}
@Override
public void debug(String msg, Throwable e) {
logger.log(FQCN, Level.DEBUG, msg, e);
}
@Override
public void info(String msg) {
logger.log(FQCN, Level.INFO, msg, null);
}
@Override
public void info(Throwable e) {
logger.log(FQCN, Level.INFO, e == null ? null : e.getMessage(), e);
}
@Override
public void info(String msg, Throwable e) {
logger.log(FQCN, Level.INFO, msg, e);
}
@Override
public void warn(String msg) {
logger.log(FQCN, Level.WARN, msg, null);
}
@Override
public void warn(Throwable e) {
logger.log(FQCN, Level.WARN, e == null ? null : e.getMessage(), e);
}
@Override
public void warn(String msg, Throwable e) {
logger.log(FQCN, Level.WARN, msg, e);
}
@Override
public void error(String msg) {
logger.log(FQCN, Level.ERROR, msg, null);
}
@Override
public void error(Throwable e) {
logger.log(FQCN, Level.ERROR, e == null ? null : e.getMessage(), e);
}
@Override
public void error(String msg, Throwable e) {
logger.log(FQCN, Level.ERROR, msg, e);
}
@Override
public boolean isTraceEnabled() {
return logger.isTraceEnabled();
}
@Override
public boolean isDebugEnabled() {
return logger.isDebugEnabled();
}
@Override
public boolean isInfoEnabled() {
return logger.isInfoEnabled();
}
@Override
public boolean isWarnEnabled() {
return logger.isEnabledFor(Level.WARN);
}
@Override
public boolean isErrorEnabled() {
return logger.isEnabledFor(Level.ERROR);
}
}
- 每个实现方法,调用
org.apache.log4j.Logger#log(org.apache.log4j.Priority, Object, Throwable)方法,进行日志的打印。其中:- 第一个参数为
FQCN,全类名,即FailsafeLogger.class.getName()。这样,我们在日志中看到的类名为FailsafeLogger,而不是Log4jLogger。 - 第二个参数为日志级别。
- 第三个参数为日志消息。
- 第四个参数为异常。
- 第一个参数为
3.2 Slf4jLoggerAdapter
com.alibaba.dubbo.common.logger.slf4j.Slf4jLoggerAdapter ,实现 LoggerAdapter 接口,Slf4j Logger 适配器实现类。
3.2.1 构造方法
Java复制private Level level;
private Logger logger = org.slf4j.LoggerFactory.getLogger(Logger.class);
public Slf4jLoggerAdapter() {
// 初始化 level 属性
String levelStr = System.getProperty("dubbo.application.logger.level");
if (levelStr != null && levelStr.length() > 0) {
level = Level.valueOf(levelStr);
} else {
level = Level.ALL;
}
}
3.2.2 getLogger
Java复制@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));
}
- 创建 Slf4jLogger 对象。在 「3.2.3 Slf4jLogger」 详细解析。
3.2.3 Slf4jLogger
com.alibaba.dubbo.common.logger.slf4j.Slf4jLogger ,实现 Logger 接口,Slf4j Logger 实现类。代码如下:
Java复制public class Slf4jLogger implements Logger {
private final org.slf4j.Logger logger;
public Slf4jLogger(org.slf4j.Logger logger) {
this.logger = logger;
}
@Override
public void trace(String msg) {
logger.trace(msg);
}
@Override
public void trace(Throwable e) {
logger.trace(e == null ? null : e.getMessage(), e);
}
@Override
public void trace(String msg, Throwable e) {
logger.trace(msg, e);
}
@Override
public void debug(String msg) {
logger.debug(msg);
}
@Override
public void debug(Throwable e) {
logger.debug(e == null ? null : e.getMessage(), e);
}
@Override
public void debug(String msg, Throwable e) {
logger.debug(msg, e);
}
@Override
public void info(String msg) {
logger.info(msg);
}
@Override
public void info(Throwable e) {
logger.info(e == null ? null : e.getMessage(), e);
}
@Override
public void info(String msg, Throwable e) {
logger.info(msg, e);
}
@Override
public void warn(String msg) {
logger.warn(msg);
}
@Override
public void warn(Throwable e) {
logger.warn(e == null ? null : e.getMessage(), e);
}
@Override
public void warn(String msg, Throwable e) {
logger.warn(msg, e);
}
@Override
public void error(String msg) {
logger.error(msg);
}
@Override
public void error(Throwable e) {
logger.error(e == null ? null : e.getMessage(), e);
}
@Override
public void error(String msg, Throwable e) {
logger.error(msg, e);
}
@Override
public boolean isTraceEnabled() {
return logger.isTraceEnabled();
}
@Override
public boolean isDebugEnabled() {
return logger.isDebugEnabled();
}
@Override
public boolean isInfoEnabled() {
return logger.isInfoEnabled();
}
@Override
public boolean isWarnEnabled() {
return logger.isWarnEnabled();
}
@Override
public boolean isErrorEnabled() {
return logger.isErrorEnabled();
}
}
- 每个实现方法,调用对应的
org.slf4j.Logger的方法,进行日志的打印。
3.3 JclLoggerAdapter
com.alibaba.dubbo.common.logger.jcl.JclLoggerAdapter ,实现 LoggerAdapter 接口,Jcl Logger 适配器实现类。
3.3.1 构造方法
Java复制private Level level;
private Logger logger = org.apache.commons.logging.LogFactory.getLog(Logger.class);
public JclLoggerAdapter() {
// 初始化 level 属性
String levelStr = System.getProperty("dubbo.application.logger.level");
if (levelStr != null && levelStr.length() > 0) {
level = Level.valueOf(levelStr);
} else {
level = Level.ALL;
}
}
3.3.2 getLogger
Java复制@Override
public Logger getLogger(String key) {
return new JclLogger(org.apache.commons.logging.LogFactory.getLog(key));
}
@Override
public Logger getLogger(Class<?> key) {
return new JclLogger(org.apache.commons.logging.LogFactory.getLog(key));
}
- 创建 JclLogger 对象。在 「3.3.3 JclLogger」 详细解析。
3.3.3 JclLogger
com.alibaba.dubbo.common.logger.jcl.JclLogger ,实现 Logger 接口,Jcl Logger 实现类。代码如下:
Java复制public class JclLogger implements Logger {
private final org.apache.commons.logging.Log logger;
public JclLogger(org.apache.commons.logging.Log logger) {
this.logger = logger;
}
@Override
public void trace(String msg) {
logger.trace(msg);
}
@Override
public void trace(Throwable e) {
logger.trace(e == null ? null : e.getMessage(), e);
}
@Override
public void trace(String msg, Throwable e) {
logger.trace(msg, e);
}
@Override
public void debug(String msg) {
logger.debug(msg);
}
@Override
public void debug(Throwable e) {
logger.debug(e == null ? null : e.getMessage(), e);
}
@Override
public void debug(String msg, Throwable e) {
logger.debug(msg, e);
}
@Override
public void info(String msg) {
logger.info(msg);
}
@Override
public void info(Throwable e) {
logger.info(e == null ? null : e.getMessage(), e);
}
@Override
public void info(String msg, Throwable e) {
logger.info(msg, e);
}
@Override
public void warn(String msg) {
logger.warn(msg);
}
@Override
public void warn(Throwable e) {
logger.warn(e == null ? null : e.getMessage(), e);
}
@Override
public void warn(String msg, Throwable e) {
logger.warn(msg, e);
}
@Override
public void error(String msg) {
logger.error(msg);
}
@Override
public void error(Throwable e) {
logger.error(e == null ? null : e.getMessage(), e);
}
@Override
public void error(String msg, Throwable e) {
logger.error(msg, e);
}
@Override
public boolean isTraceEnabled() {
return logger.isTraceEnabled();
}
@Override
public boolean isDebugEnabled() {
return logger.isDebugEnabled();
}
@Override
public boolean isInfoEnabled() {
return logger.isInfoEnabled();
}
@Override
public boolean isWarnEnabled() {
return logger.isWarnEnabled();
}
@Override
public boolean isErrorEnabled() {
return logger.isErrorEnabled();
}
}
- 每个实现方法,调用对应的
org.apache.commons.logging.Log的方法,进行日志的打印。
3.4 JdkLoggerAdapter
com.alibaba.dubbo.common.logger.jdk.JdkLoggerAdapter ,实现 LoggerAdapter 接口,Jdk Logger 适配器实现类。
3.4.1 构造方法
Java复制private Level level;
private Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName());
public JdkLoggerAdapter() {
// 初始化 level 属性
String levelStr = System.getProperty("dubbo.application.logger.level");
if (levelStr != null && levelStr.length() > 0) {
level = Level.valueOf(levelStr);
} else {
level = Level.ALL;
}
}
3.4.2 getLogger
Java复制@Override
public Logger getLogger(String key) {
return new JdkLogger(java.util.logging.Logger.getLogger(key));
}
@Override
public Logger getLogger(Class<?> key) {
return new JdkLogger(java.util.logging.Logger.getLogger(key.getName()));
}
- 创建 JdkLogger 对象。在 「3.4.3 JdkLogger」 详细解析。
3.4.3 JdkLogger
com.alibaba.dubbo.common.logger.jdk.JdkLogger ,实现 Logger 接口,Jdk Logger 实现类。代码如下:
Java复制public class JdkLogger implements Logger {
private final java.util.logging.Logger logger;
public JdkLogger(java.util.logging.Logger logger) {
this.logger = logger;
}
@Override
public void trace(String msg) {
logger.log(Level.FINER, msg);
}
@Override
public void trace(Throwable e) {
logger.log(Level.FINER, e == null ? null : e.getMessage(), e);
}
@Override
public void trace(String msg, Throwable e) {
logger.log(Level.FINER, msg, e);
}
@Override
public void debug(String msg) {
logger.log(Level.FINE, msg);
}
@Override
public void debug(Throwable e) {
logger.log(Level.FINE, e == null ? null : e.getMessage(), e);
}
@Override
public void debug(String msg, Throwable e) {
logger.log(Level.FINE, msg, e);
}
@Override
public void info(String msg) {
logger.log(Level.INFO, msg);
}
@Override
public void info(Throwable e) {
logger.log(Level.INFO, e == null ? null : e.getMessage(), e);
}
@Override
public void info(String msg, Throwable e) {
logger.log(Level.INFO, msg, e);
}
@Override
public void warn(String msg) {
logger.log(Level.WARNING, msg);
}
@Override
public void warn(Throwable e) {
logger.log(Level.WARNING, e == null ? null : e.getMessage(), e);
}
@Override
public void warn(String msg, Throwable e) {
logger.log(Level.WARNING, msg, e);
}
@Override
public void error(String msg) {
logger.log(Level.SEVERE, msg);
}
@Override
public void error(Throwable e) {
logger.log(Level.SEVERE, e == null ? null : e.getMessage(), e);
}
@Override
public void error(String msg, Throwable e) {
logger.log(Level.SEVERE, msg, e);
}
@Override
public boolean isTraceEnabled() {
return logger.isLoggable(Level.FINER);
}
@Override
public boolean isDebugEnabled() {
return logger.isLoggable(Level.FINE);
}
@Override
public boolean isInfoEnabled() {
return logger.isLoggable(Level.INFO);
}
@Override
public boolean isWarnEnabled() {
return logger.isLoggable(Level.WARNING);
}
@Override
public boolean isErrorEnabled() {
return logger.isLoggable(Level.SEVERE);
}
}
- 每个实现方法,调用对应的
java.util.logging.Logger的方法,进行日志的打印。
4. Level
com.alibaba.dubbo.common.logger.Level ,日志级别枚举。代码如下:
Java复制public enum Level {
/**
* ALL
*/
ALL,
/**
* TRACE
*/
TRACE,
/**
* DEBUG
*/
DEBUG,
/**
* INFO
*/
INFO,
/**
* WARN
*/
WARN,
/**
* ERROR
*/
ERROR,
/**
* OFF
*/
OFF
}
5. Logger
com.alibaba.dubbo.common.logger.Logger ,日志接口。代码如下:
Java复制public interface Logger {
/**
* 输出跟踪信息
*
* @param msg 信息
*/
void trace(String msg);
/**
* 输出跟踪信息
*
* @param e 异常
*/
void trace(Throwable e);
/**
* 输出跟踪信息
*
* @param msg 信息
* @param e 异常
*/
void trace(String msg, Throwable e);
/**
* 输出调试信息
*
* @param msg 信息
*/
void debug(String msg);
/**
* 输出调试信息
*
* @param e 异常
*/
void debug(Throwable e);
/**
* 输出调试信息
*
* @param msg 信息
* @param e 异常
*/
void debug(String msg, Throwable e);
/**
* 输出普通信息
*
* @param msg 信息
*/
void info(String msg);
/**
* 输出普通信息
*
* @param e 异常
*/
void info(Throwable e);
/**
* 输出普通信息
*
* @param msg 信息
* @param e 异常
*/
void info(String msg, Throwable e);
/**
* 输出警告信息
*
* @param msg 信息
*/
void warn(String msg);
/**
* 输出警告信息
*
* @param e 异常
*/
void warn(Throwable e);
/**
* 输出警告信息
*
* @param msg 信息
* @param e 异常
*/
void warn(String msg, Throwable e);
/**
* 输出错误信息
*
* @param msg 信息
*/
void error(String msg);
/**
* 输出错误信息
*
* @param e 异常
*/
void error(Throwable e);
/**
* 输出错误信息
*
* @param msg 信息
* @param e 异常
*/
void error(String msg, Throwable e);
/**
* 跟踪信息是否开启
*
* @return 是否开启
*/
boolean isTraceEnabled();
/**
* 调试信息是否开启
*
* @return 是否开启
*/
boolean isDebugEnabled();
/**
* 普通信息是否开启
*
* @return 是否开启
*/
boolean isInfoEnabled();
/**
* 警告信息是否开启
*
* @return 是否开启
*/
boolean isWarnEnabled();
/**
* 错误信息是否开启
*
* @return 是否开启
*/
boolean isErrorEnabled();
}
666. 彩蛋

😈 又是一篇水文,嘿嘿。