在日志中記錄Java異常信息的正確姿勢分享
日志中記錄Java異常信息
遇到的問題
今天遇到一個線上的BUG,在執(zhí)行表單提交時失敗,但是從程序日志中看不到任何異常信息。
在Review源代碼時發(fā)現(xiàn),當(dāng)catch到異常時只是輸出了e.getMessage(),如下所示:
logger.error("error: {}, {}", params, e.getMessage());
在日志中看不到任何信息,說明e.getMessage()返回值為空字符串。
原因分析
先來看一下Java中的異常類圖:
Throwable是Java中所有異常信息的頂級父類,其中的成員變量detailMessage就是在調(diào)用e.getMessage()返回的值。
那么這個屬性會在什么時候賦值呢,追溯源碼發(fā)現(xiàn),該屬性只會在Throwable構(gòu)造函數(shù)中賦值。
public Throwable() { // 在默認(rèn)構(gòu)造函數(shù)中不會給detailMessage屬性賦值 fillInStackTrace(); } public Throwable(String message) { fillInStackTrace(); // 直接將參數(shù)賦值給detailMessage detailMessage = message; } public Throwable(String message, Throwable cause) { fillInStackTrace(); // 直接將參數(shù)賦值給detailMessage detailMessage = message; this.cause = cause; } public Throwable(Throwable cause) { fillInStackTrace(); // 當(dāng)傳入的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; } // 直接將參數(shù)賦值給detailMessage detailMessage = message; this.cause = cause; if (!enableSuppression) suppressedExceptions = null; }
顯然,從源碼中可以看到在Throwable的默認(rèn)構(gòu)造函數(shù)中是不會給detailMessage屬性賦值的。
也就是說,當(dāng)異常對象是通過默認(rèn)構(gòu)造函數(shù)實例化的,或者實例化時傳入的message為空字符串,那么調(diào)用getMessage()方法時返回值就為空,也就是我遇到的情形。
所以,在程序日志中不要單純使用getMessage()方法獲取異常信息(返回值為空時,不利于問題排查)。
正確的做法
在Java開發(fā)中,常用的日志框架及組件通常是:slf4j,log4j和logback,他們的關(guān)系可以描述為:slf4j提供了統(tǒng)一的日志API,將具體的日志實現(xiàn)交給log4j與logback。
也就是說,通常我們只需要在項目中使用slf4j作為日志API,再集成log4j或者logback即可。
<!-- 使用slf4j作為日志API --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <!-- 集成logback作為具體的日志實現(xiàn) --> <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為例,添加對應(yīng)的logback配置文件(logback.xml):
<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記錄日志:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Test { private static final Logger logger = LoggerFactory.getLogger(Test.class); }
當(dāng)我們需要在程序日志中輸出異常信息時,應(yīng)該直接傳入異常對象即可,而不要單純通過異常對象的getMessage()方法獲取輸出異常信息。
public void test() { try { // 使用默認(rèn)構(gòu)造函數(shù)實實例化異常對象 throw new NullPointerException(); } catch (Exception e) { // 直接將異常對象傳入日志接口,保存異常信息到日志文件中 logger.error("error: {}", e.getMessage(), e); e.printStackTrace(); } }
如下是保存到日志文件中的異常信息片段:
2019-06-20 20:04:25,290 ERROR [http-nio-8090-exec-1] o.c.s.f.c.TestExceptionController [TestExceptionController.java:26] error: null # 使用默認(rèn)構(gòu)造參數(shù)實例化異常對象時,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()打印的內(nèi)容是什么
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(); } }
輸出結(jié)果如下:
java.lang.ArithmeticException: / by zero
at myProject.ExceptionTest.test(ExceptionTest.java:10)
at myProject.ExceptionTest.main(ExceptionTest.java:18)
可見,e.printStackTrace()打印了錯誤的具體信息,即這個錯誤出現(xiàn)的位置,便于查找錯誤源
3、如果將e.printStackTrace()的信息打印在日志里應(yīng)該怎么做呢?
見如下代碼:
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);打印日志,輸出結(jié)果如下:
19:17:39.753 [main] ERROR myProject.ExceptionTest - java.lang.ArithmeticException: / by zero
可見,用這種方法打印的日志,只有大概的錯誤信息,并沒有指出報錯的代碼位置,不便于查找錯誤。用logger.error(e.getMessage());也是輸出這種大概的錯誤信息。
再見如下代碼:
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);,則輸出結(jié)果如下:
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()打印的內(nèi)容大致是相同的。不過最好,還是使用logger.error(e.getMessage(),e)方法來在日志上查看異常的詳細(xì)結(jié)果
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何在Intellij中安裝LeetCode刷題插件方便Java刷題
這篇文章主要介紹了如何在Intellij中安裝LeetCode刷題插件方便Java刷題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08Mybatis查詢Sql結(jié)果未映射到對應(yīng)得實體類上的問題解決
使用mybatis查詢表數(shù)據(jù)得時候,發(fā)現(xiàn)對應(yīng)得實體類字段好多都是null,本文主要介紹了Mybatis查詢Sql結(jié)果未映射到對應(yīng)得實體類上的問題解決,具有一定的參考價值,感興趣的可以了解一下2024-02-02SpringCloud?分布式微服務(wù)架構(gòu)操作步驟
SpringCloud是一種微服務(wù)的框架,利用它我們可以去做分布式服務(wù)開發(fā),這篇文章主要介紹了SpringCloud?分布式微服務(wù)架構(gòu),需要的朋友可以參考下2022-07-07