一、记录日志的几种方式比较
为了测试,我在测试类中写了七种打印方式,分别如下:
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ErrorLogTest {
@Test
public void ss(){
try{
float xx= 1/0;
log.info("xx:{}",xx);
}catch (Exception e){
log.info("======================第一种打印LogExceptionStackUtil.logExceptionStack(e)======================");
log.error("异常:{}",LogExceptionStackUtil.logExceptionStack(e));
log.info("======================第二种打印e======================");
log.error("异常:{}",e);
log.info("======================第三种打印e.printStackTrace()======================");
e.printStackTrace();
log.info("======================第四种打印e.getMessage()======================");
log.error("异常:{}",e.getMessage());
log.info("======================第五种打印e.getStackTrace()======================");
log.error("异常:{}",e.getStackTrace());
log.info("======================第六种打印e.toString()======================");
log.error("异常:{}",e.toString());
log.info("======================第七种打印System.out.println======================");
System.out.println("异常:{}"+e);
}
}
}
我在工程中使用的是logback框架进行日志记录。
注意:一定要让spring容器启动起来,要不然你会苦恼得发现日志文件没生成(@RunWith和@SpringBootTest注解不要删)。
运行起来后,先给大家总结下效果吧:
方式 | 效果 |
---|---|
log.error(“异常:{}”,LogExceptionStackUtil.logExceptionStack(e)); | 会打印完整日志,且打印的报错行和具体日志的开始在同一行 |
log.error(“异常:{}”,e); | 会打印完整日志,但打印的报错行和具体日志的开始不在同一行,如果用某些特殊搜索,会遗漏完整日志 |
e.printStackTrace(); | 只在控制台打印了,日志文件中没有记录 |
log.error(“异常:{}”,e.getMessage()); | 没有异常类型,只有异常的简单信息 |
log.error(“异常:{}”,e.getStackTrace()); | 没有异常类型,只会打印从哪一行抛出的 |
log.error(“异常:{}”,e.toString()); | 包括异常类型和异常的简单信息 |
System.out.println(“异常:{}”+e); | 只在控制台打印了(包括异常类型和异常的简单信息),日志文件中没有记录 |
日志文件节选(此处吐槽优快云不能让用户选择折叠和展开):
2022-02-21 17:13:07.392 [main] INFO com.my.demojava.controller.ErrorLogTest.ss line:31 - ======================第一种打印LogExceptionStackUtil.logExceptionStack(e)======================
2022-02-21 17:13:07.393 [main] ERROR com.my.demojava.controller.ErrorLogTest.ss line:32 - 异常:java.lang.ArithmeticException: / by zero
at com.my.demojava.controller.ErrorLogTest.ss(ErrorLogTest.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
此处略去30行
2022-02-21 17:13:07.394 [main] INFO com.my.demojava.controller.ErrorLogTest.ss line:33 - ======================第二种打印e======================
2022-02-21 17:13:07.397 [main] ERROR com.my.demojava.controller.ErrorLogTest.ss line:34 - 异常:{}
java.lang.ArithmeticException: / by zero
at com.my.demojava.controller.ErrorLogTest.ss(ErrorLogTest.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
此处略去30行
2022-02-21 17:13:07.397 [main] INFO com.my.demojava.controller.ErrorLogTest.ss line:35 - ======================第三种打印e.printStackTrace()======================
2022-02-21 17:13:07.399 [main] INFO com.my.demojava.controller.ErrorLogTest.ss line:37 - ======================第四种打印e.getMessage()======================
2022-02-21 17:13:07.399 [main] ERROR com.my.demojava.controller.ErrorLogTest.ss line:38 - 异常:/ by zero
2022-02-21 17:13:07.399 [main] INFO com.my.demojava.controller.ErrorLogTest.ss line:39 - ======================第五种打印e.getStackTrace()======================
2022-02-21 17:13:07.399 [main] ERROR com.my.demojava.controller.ErrorLogTest.ss line:40 - 异常:com.my.demojava.controller.ErrorLogTest.ss(ErrorLogTest.java:28)
2022-02-21 17:13:07.400 [main] INFO com.my.demojava.controller.ErrorLogTest.ss line:41 - ======================第六种打印e.toString()======================
2022-02-21 17:13:07.400 [main] ERROR com.my.demojava.controller.ErrorLogTest.ss line:42 - 异常:java.lang.ArithmeticException: / by zero
2022-02-21 17:13:07.400 [main] INFO com.my.demojava.controller.ErrorLogTest.ss line:43 - ======================第七种打印System.out.println======================
总结:最推荐的做法是第一种打印方式:log.error(“异常:{}”,LogExceptionStackUtil.logExceptionStack(e));
public class LogExceptionStackUtil {
public static String logExceptionStack(Throwable e) {
StringWriter errorsWriter = new StringWriter();
e.printStackTrace(new PrintWriter(errorsWriter));
return errorsWriter.toString();
}
}
或者使用最常用的第二种打印方式:log.error(“异常:{}”,e);
如果你不想略去详细信息,请不要用第四、第五、第六种;
最最最不应该使用的是第三种e.printStackTrace(),因为它压根不会在日志文件里打印,且容易堆栈溢出,造成第二节的严重后果,见下。
二、e.printStackTrace()为什么不要用
1)不会打印到日志文件中,生产环境排查问题的噩梦
只会打印在控制台,如果生产环境有问题,莫非要连上人家的环境启动项目?或者临时改代码?(说多了都是泪)
2)占用内存,严重时造成系统卡顿或挂掉
具体原因如下流程:
短时间内大量请求访问此接口 ->
代码本身有问题,很多情况下抛异常 ->
e.printStackTrace() 来打印异常到控制台 ->
产生错误堆栈字符串到字符串池内存空间 ->
此内存空间一下子被占满了 ->
开始在此内存空间产出字符串的线程还没完全生产完整,就没空间了 ->
大量线程产出字符串产出到一半,等在这儿->
相互等待 ->
等内存->
锁死了->
整个应用挂掉、访问没响应
参考文章:java中e.printStackTrace()不要使用,请使用logger记录
3)日志交错混合,不易读
printStackTrace()默认使用了System.err输出流进行输出,与System.out是两个不同的输出流,那么在打印时自然就形成了交叉。再就是输出流是有缓冲区的,所以对于什么时候具体输出也形成了随机。
三、升级logback或log4j2版本的简易方法
1)升级logback版本到1.2.10
在pom.xml中,< properties >标签中加入配置:
<logback.version>1.2.10</logback.version>
2)升级log4j2版本到2.17.0
在pom.xml中,< properties >标签中加入配置:
<log4j2.version>2.17.0</log4j2.version>