好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

在日志中记录Java异常信息的正确姿势分享

日志中记录Java异常信息

遇到的问题

今天遇到一个线上的BUG,在执行表单提交时失败,但是从程序日志中看不到任何异常信息。
在Review源代码时发现,当catch到异常时只是输出了e.getMessage(),如下所示:

?

1

logger.error( "error: {}, {}" , params, e.getMessage());

在日志中看不到任何信息,说明e.getMessage()返回值为空字符串。

原因分析

先来看一下Java中的异常类图:

Throwable是Java中所有异常信息的顶级父类,其中的成员变量detailMessage就是在调用e.getMessage()返回的值。
那么这个属性会在什么时候赋值呢,追溯源码发现,该属性只会在Throwable构造函数中赋值。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

public Throwable() {

     // 在默认构造函数中不会给detailMessage属性赋值

     fillInStackTrace();

}

public Throwable(String message) {

     fillInStackTrace();

     // 直接将参数赋值给detailMessage

     detailMessage = message;

}

public Throwable(String message, Throwable cause) {

     fillInStackTrace();

     // 直接将参数赋值给detailMessage

     detailMessage = message;

     this .cause = cause;

}

public Throwable(Throwable cause) {

     fillInStackTrace();

     // 当传入的Throwable对象不为空时,为detailMessage赋值

     detailMessage = (cause== null ? null : cause.toString());

     this .cause = cause;

}

protected Throwable(String message, Throwable cause,

                         boolean enableSuppression,

                         boolean writableStackTrace) {

     if (writableStackTrace) {

         fillInStackTrace();

     } else {

         stackTrace = null ;

     }

     // 直接将参数赋值给detailMessage

     detailMessage = message;

     this .cause = cause;

     if (!enableSuppression)

         suppressedExceptions = null ;

}

显然,从源码中可以看到在Throwable的默认构造函数中是不会给detailMessage属性赋值的。

也就是说,当异常对象是通过默认构造函数实例化的,或者实例化时传入的message为空字符串,那么调用getMessage()方法时返回值就为空,也就是我遇到的情形。

所以,在程序日志中不要单纯使用getMessage()方法获取异常信息(返回值为空时,不利于问题排查)。

正确的做法

在Java开发中,常用的日志框架及组件通常是:slf4j,log4j和logback,他们的关系可以描述为:slf4j提供了统一的日志API,将具体的日志实现交给log4j与logback。

也就是说,通常我们只需要在项目中使用slf4j作为日志API,再集成log4j或者logback即可。


?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<!-- 使用slf4j作为日志API -->

< dependency >

     < groupId >org.slf4j</ groupId >

     < artifactId >slf4j-api</ artifactId >

     < version >1.7.25</ version >

</ dependency >

<!-- 集成logback作为具体的日志实现 -->

< dependency >

     < groupId >ch.qos.logback</ groupId >

     < artifactId >logback-core</ artifactId >

     < version >1.2.3</ version >

</ dependency >

< dependency >

     < groupId >ch.qos.logback</ groupId >

     < artifactId >logback-classic</ artifactId >

     < version >1.2.3</ version >

</ dependency >

上述配置以集成slf4j和logback为例,添加对应的logback配置文件(logback.xml):

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

< configuration scan = "false" scanPeriod = "30 seconds" debug = "false" packagingData = "true" >

     < statusListener class = "ch.qos.logback.core.status.OnConsoleStatusListener" />

     <!-- 输出到控制台 -->

     < appender name = "STDOUT" class = "ch.qos.logback.core.ConsoleAppender" >

         < encoder >

             < pattern >%date %level [%thread] %logger{10} [%file:%line] %msg%n</ pattern >

         </ encoder >

     </ appender >

       

     <!-- 输出到文件 -->

     < appender name = "FILE" class = "ch.qos.logback.core.FileAppender" >

         < file >test.log</ file >

         < encoder >

             < pattern >%date %level [%thread] %logger{10} [%file:%line] %msg%n</ pattern >

         </ encoder >

     </ appender >

     < root level = "info" >

         < appender-ref ref = "STDOUT" />

         < appender-ref ref = "FILE" />

     </ root >

</ configuration >

在Java中通过slf4j提供的日志API记录日志:

?

1

2

3

4

5

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class Test {

     private static final Logger logger = LoggerFactory.getLogger(Test. class );

}

当我们需要在程序日志中输出异常信息时,应该直接传入异常对象即可,而不要单纯通过异常对象的getMessage()方法获取输出异常信息。

?

1

2

3

4

5

6

7

8

9

10

public void test() {

     try {

         // 使用默认构造函数实实例化异常对象

         throw new NullPointerException();

     } catch (Exception e) {

         // 直接将异常对象传入日志接口,保存异常信息到日志文件中

         logger.error( "error: {}" , e.getMessage(), e);

         e.printStackTrace();

     }

}

如下是保存到日志文件中的异常信息片段:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

2019 - 06 - 20 20 : 04 : 25 , 290 ERROR [http-nio- 8090 -exec- 1 ] o.c.s.f.c.TestExceptionController [TestExceptionController.java: 26 ] error: null # 使用默认构造参数实例化异常对象时,getMessage()方法返回值为空对象

# 如下是具体的异常堆栈信息

java.lang.NullPointerException: null

  at org.chench.springboot.falsework.controller.TestExceptionController.test(TestExceptionController.java: 24 ) ~[classes/:na]

  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na: 1.8 .0_181]

  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62 ) ~[na: 1.8 .0_181]

  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 ) ~[na: 1.8 .0_181]

  at java.lang.reflect.Method.invoke(Method.java: 498 ) ~[na: 1.8 .0_181]

  at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java: 209 ) [spring-web- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java: 136 ) [spring-web- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java: 102 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java: 877 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java: 783 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java: 87 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java: 991 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java: 925 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java: 974 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java: 866 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at javax.servlet.http.HttpServlet.service(HttpServlet.java: 635 ) [tomcat-embed-core- 8.5 . 31 .jar: 8.5 . 31 ]

  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java: 851 ) [spring-webmvc- 5.0 . 6 .RELEASE.jar: 5.0 . 6 .RELEASE]

  at javax.servlet.http.HttpServlet.service(HttpServlet.java: 742 ) [tomcat-embed-core- 8.5 . 31 .jar: 8.5 . 31 ]

......

java异常在控制台和日志里面的打印记录

1、e.printStackTrace()打印在哪里

在catch中的e.printStackTrace()将打印到控制台

2、e.printStackTrace()打印的内容是什么

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import org.apache.logging.log4j.Logger;

public class ExceptionTest {

     private static final Logger logger=LogManager.getLogger();

     public void   test() {

         try {

             int i= 1 / 0 ;           

         } catch (Exception e){   

             e.printStackTrace();

         }

     }

     public static void main(String[] args) {

         ExceptionTest test= new ExceptionTest();

         test.test();       

     }   

}

输出结果如下:

java.lang.ArithmeticException: / by zero
at myProject.ExceptionTest.test(ExceptionTest.java:10)
at myProject.ExceptionTest.main(ExceptionTest.java:18)

可见,e.printStackTrace()打印了错误的具体信息,即这个错误出现的位置,便于查找错误源

3、如果将e.printStackTrace()的信息打印在日志里应该怎么做呢?

见如下代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

package myProject;

import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

public class ExceptionTest {

     private static final Logger logger=LogManager.getLogger();

     public void   test() {

         try {

             int i= 1 / 0 ;           

         } catch (Exception e){   

             logger.error(e);

         }

     }

     public static void main(String[] args) {

         ExceptionTest test= new ExceptionTest();

         test.test();       

     }   

}

用logger.error(e);打印日志,输出结果如下:

19:17:39.753 [main] ERROR myProject.ExceptionTest - java.lang.ArithmeticException: / by zero

可见,用这种方法打印的日志,只有大概的错误信息,并没有指出报错的代码位置,不便于查找错误。用logger.error(e.getMessage());也是输出这种大概的错误信息。

再见如下代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

package myProject;

import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

public class ExceptionTest {

     private static final Logger logger=LogManager.getLogger();

     public void   test() {

         try {

             int i= 1 / 0 ;           

         } catch (Exception e){   

             logger.error( "ExceptionTest Exception:" ,e);

         }

     }

     public static void main(String[] args) {

         ExceptionTest test= new ExceptionTest();

         test.test();       

     }   

}

用logger.error("ExceptionTest Exception:",e);,则输出结果如下:

9:20:32.948 [main] ERROR myProject.ExceptionTest - ExceptionTest Exception:
java.lang.ArithmeticException: / by zero
at myProject.ExceptionTest.test(ExceptionTest.java:10) [classes/:?]
at myProject.ExceptionTest.main(ExceptionTest.java:18) [classes/:?]

这和e.printStackTrace()打印的内容大致是相同的。不过最好,还是使用logger.error(e.getMessage(),e)方法来在日志上查看异常的详细结果

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://jingzh.blog.csdn.net/article/details/81410992

查看更多关于在日志中记录Java异常信息的正确姿势分享的详细内容...

  阅读:32次