引言
平常自己try-catch异常的时候,经常为了方便就写一个e.printStackTrace,如果出现bug,虽然经过一段翻找也能定位到问题产生的位置,但是这其实只是因为自己平常练习的项目就是一个小demo,甚至是玩具级别的东西,直接e.printStackTrace也不会出现什么太大的问题。最近在学log4j2和slf4j的区别时,偶然看到e.printStackTrace是有缺陷的:
1、占用内存太多,容易造成死锁
- 因为e.printStackTrace是直接打印到控制台上,产生的字符串要存放在字符串常量池中,而字符串常量池在JDK1.8之后存放在堆中,此时字符串常量池的大小受限于堆的大小,而如果e.printStackTrace产生的堆栈字符串信息如果太多,会导致内存空间严重不足,后续的请求就会因此被阻塞住了
2、日志交错混合,不易读
- e.printStackTrace默认使用了System.err输出流进行输出,与System.out是两个不同的输出流,在打印时自然就形成了交叉。由于输出流是有缓冲区的,交叉的两个流会导致输出随机化
因而打印日志的时候,最好还是使用日志框架slf4j。以下是几个常见的日志打印建议,这里进行一个记录以备后续查找
日志打印的一些建议
1、日志打印要打印入参、出参关键信息
方法进来时,打印关键的入参信息,比如userId等,方法返回的时候,打印出参,例如:
public String getTestCaseInfo(Case case){
log.debug("Case name:{}",case.name);
String message = "2333";
log.debug("return value:{}",message);
return message;
}
2、配置合适的日志格式
参考的logback配置:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n</pattern>
</encoder>
</appender>
日志应该包含诸如当前时间、线程名、日志级别等信息
3、日志级别较低时,利用开关判断是否打印
User user = new User(666L, "DestiNation");
if (log.isDebugEnabled()) {
log.debug("userName: {}", user.getId());
}
4、使用slf4j日志框架API
slf4j是一个统一的门面式的日志框架,底层日志系统(log4j、log4j2、logback等)可以更换,一般而言,日志系统的接口都是有一定差别的,而使用slf4j可以在不更改代码的情况下,统一对外的调用接口
//方法一,注解
@slf4j
//方法二
private static final Logger logger = LoggerFactory.getLogger(Use.class);
5、使用占位符
这个不用多说,如果直接使用+号进行字符串拼接会造成性能损失(拼接字符串一般也是用StringBuffer或者StringBuilder),而使用占位符{}仅是替换操作,有效提升性能
logger.info("User id: {}, name : {} ", id, name);
6、要输出全部的错误信息
不要用e.getMessage(),这个方法只会记录基本的错误描述,没有具体的堆栈信息,有可能不利于问题的排查,正确做法是直接输出e
try {
//执行的业务代码
} catch (Exception e) {
logger.error("catch exception: ", e);
}
7、日志文件分离
- 根据不同的类型分离,如error、warn不同等级
- 根据不同的业务分离
8、可以使用异步方式输出日志
日志输出要用到输出流,异步可以提升IO性能
logback参考配置:
<appender name="FILE_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="ASYNC"/>
</appender>