一、日志级别
org.apache.log4j.Level的日志级别总共有8种,从All到Off,日志输出优先级依次变高。通过日志级别,可以控制到应用程序中相应级别的日志信息的开关。比如应用中定义日志级别是Info,那么在Info级别以下的日志就不会输出,即Debug、Trace等级别的日志不会被输出。
日志级别的含义:
- OFF:最高级别,关闭日志,不打印日志。
- FATAL:致命日志,指明非常严重的可能会导致应用终止执行错误事件。
- ERROR:错误日志,这种级别的错误是系统无法容忍的。比如:空指针异常,数据库不可用等。
- WARN:警告日志,可能潜在的危险状况。比如:"当前数据不可用,使用缓存数据"。
- INFO:信息日志,指明描述信息,从粗粒度上描述了应用运行过程。
- DEBUG:调试日志,指明细致的事件信息,对调试应用最有用。该级别日志主要用于在开发、测试阶段输出。该级别的日志应尽可能地详尽,开发人员可以将各类详细信息记录到 DEBUG 里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等等。
- TRACE:跟踪日志,指明程序运行轨迹,比DEBUG级别的粒度更细。
- ALL:所有日志级别,包括定制级别。
二、日志规范
代码中的日志输出需要注意一些事项:
1、使用lombok的@Slf4j注解
推荐使用lombok的@Slf4j注解,它可以自动生成日志变量实例。
public class TestClass {
private static final Logger log = LoggerFactory.getLogger(TestClass.class);
}
2、输出Exception里的全部Throwable信息
打印错误日志时,需要将错误的堆栈全部打印出来,不能将堆栈信息打印成文本。
//正例
try {
a();
} catch (Exception e) {
log.error("invoke error ", e);
//log.error("invoke msg:{} ", ex.getMessage(),e);
}
//反例
try {
a();
} catch (Exception e) {
log.error("invoke error errorMessage:{}", e.getMessage());
}
3、禁止记录日志后又抛出异常
记录日志后又抛出异常会导致多次打印错误日志,浪费磁盘空间。
//正例
try {
b();
} catch (Exception e) {
throw new BizException("方法异常", e);
}
//反例
try {
b();
} catch (Exception e) {
log.error("invoke error ", e);
throw new BizException("方法异常", e);
}
4、禁止出现System print
代码里禁止使用System.out.println和System.error.println语句。因为调用这两个方法没法将日志信息统一打印到日志文件里,导致日志丢失。
5、禁止出现printStackTrace
代码里不允许出现printStackTrace,printStackTrace打印出的堆栈日志跟正常输出或者业务代码执行日志是交错混合在一起的,在并发大日志输出多的情况下,查看异常日志就变更的非常困难,因为一块日志都不在一起了。而且刷控制台的时候如果堆栈信息过多,可能导致内存浪费。
//反例
try {
b();
} catch (Exception e) {
e.printStackTrace();
}
6、 需要考虑日志性能
如果代码较为核心,并且执行频率也高,可以考虑使用低优先级的级别,如使用info,debug等。或者加条件判断。如下所示:
if (log.isDebugEnabled) {
log.debug("XXX {}", "mss");
}
7、info日志中,建议使用占位符替换字符串拼接
使用字符串拼接不仅影响效率,更影响代码可读性。
//正例
log.info("invoke success userId:{}, userName:{}", userId, userName);
//反例
log.info("invoke success userId:" + userId + ", userName:" + userName);
8、禁止在循环语句中打印日志
在循环语句中打印日志会造成打印日志时的多次IO,从而降低代码性能。如果非要将for循环中的信息打印出来,建议在内存中统一记录,在循环外进行打印。
//正例
int successCount = 0;
for(int i=0; i<2000; i++) {
//省略代码
if (success) {
successCount++;
}
}
log.info("成功{}次", successCount);
//反例
int successCount = 0;
for(int i=0; i<2000; i++){
//省略代码
if (success) {
successCount++;
}
log.info("第{}次调用,成功{}次", i, successCount);
}
9、禁止打印集合信息
有时候方法接口可能返回一个集合信息,而集合中的每个元素可能包含了很多字段,这样就导致大量信息被打印,造成内存和磁盘的消耗,有时候可能会导致内存溢出,磁盘耗尽。建议打印集合的id信息(如果userIds太大,比如超过几百上千的,也要避免打印)或者只打印入参信息。
//正例
private List<User> getUserList(String param) {
List<User> userList = getUserListFromDB(param);
List<Long> userIds = extractUserIds(userList);
//如果userIds太大也要避免打印
log.inf(" userIds:{}", JSON.toString(userIds));
return userList;
}
//或者只打印入参
private List<User> getUserList(String param) {
List<User> userList = getUserListFromDB(param);
log.inf(" param:{}", param);
return userList;
}
//反例
private List<User> getUserList(String param) {
List<User> userList = getUserListFromDB(param);
log.inf(" userList:{}", JSON.toString(userList));
return userList;
}
10、禁止打印敏感信息
日志中尽量不要包含敏感信息,对于敏感信息如用户身份证号码,密码可以加密后存储;以防止日志文件不慎外泄时保全用户的数据安全。
11、禁止打印没有意义的日志
不记录对于排查故障毫无意义的日志信息,日志信息一定要带有业务信息。
//正例
public void handleLogic(Long userId) {
log.info("handleLogic begin userId:{}", userId);
//...
}
//反例
public void handleLogic(Long userId) {
log.info("handleLogic begin ");
//...
}
12、禁止打印日志的代码出现bug
打印日志的时候,代码不能有bug,否则影响正常业务。如NPE等。
//正例
public void handleLogic(User user) {
//user判空
log.info("handleLogic begin userId:{}", user != null ? user.getId() : "");
//...
}
//反例
public void handleLogic(User user) {
//此处如果user为null会报NPE,导致后续业务无法执行
log.info("handleLogic begin userId:{}", user.getId());
//...
}
13、区别对待日志
常用的日志级别有Debug、Info、Warn、Error。
Error日志:记录系统逻辑错误、异常或违反重要业务规则的日志信息(需要人工干预)。
Warn日志:记录一些业务异常可以通过引导重试就能恢复正常的日志信息,如用户输入参数错误。
Info日志:记录系统关键信息,如初始化系统配置、业务状态变化,或者用户业务流程中的核心处理等,方便日常运维工作以及错误回溯时上下文场景复现。
Debug日志:主要用于在开发、测试阶段输出。该级别的日志应尽可能将各类详细信息记录到 Debug 里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等,禁止在线上环境使用Debug日志。