一、记录日志的几种方式比较
为了测试,我在测试类中写了七种打印方式,分别如下:
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ErrorLogTest {@Testpublic 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 zeroat 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 zeroat 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是两个不同的输出流,那么在打印时自然就形成了交叉。再就是输出流是有缓冲区的,所以对于什么时候具体输出也形成了随机。