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

目录
  •  日志中记录Java异常信息
    •  遇到的问题
    •  原因分析
    •  正确的做法
  •  java异常在控制台和日志里面的打印记录
    •  1、e.printStackTrace()打印在哪里
    •  2、e.printStackTrace()打印的内容是什么
    •  3、如果将e.printStackTrace()的信息打印在日志里应该怎么做呢?

日志中记录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)方法来在日志上查看异常的详细结果

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java可以使用java.util.logging标准日志框架将报错信息输出到日志文件。具体实现方法如下: 1. 创建Logger对象 ```java private static final Logger LOGGER = Logger.getLogger(YourClassName.class.getName()); ``` 2. 在代码使用Logger对象记录错误信息 ```java try { // your code here } catch (Exception e) { LOGGER.log(Level.SEVERE, "An error occurred", e); } ``` 在上面的代码,Level.SEVERE 表示记录严重错误信息,"An error occurred" 是错误信息的描述,e 是捕获到的异常对象。 3. 在配置文件设置日志输出 ```properties handlers= java.util.logging.FileHandler java.util.logging.FileHandler.pattern = /path/to/log/file.log java.util.logging.FileHandler.level = ALL java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter ``` 在上面的配置文件,/path/to/log/file.log 是你想要输出日志的文件路径。你也可以在代码设置日志输出路径,如下所示: ```java Handler fileHandler = new FileHandler("/path/to/log/file.log"); LOGGER.addHandler(fileHandler); ``` 这样,在代码的所有错误信息都会被记录日志文件,包括异常的堆栈信息。如果你想要更详细的堆栈信息,可以在配置文件日志级别设置为ALL。 ```properties java.util.logging.FileHandler.level = ALL ``` 这样可以记录所有日志信息,包括FINE、FINER和FINEST级别的信息。当然,在生产环境,我们通常只记录SEVERE、WARNING和INFO级别的信息,以避免日志文件过大。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值