在SpringBoot项目的Controller类里,在微信支付成功后,客户端向服务端查询订单结果的方法里面,希望将订单业务逻辑过程中的异常捕获,然后构建自己的异常类(MessageException),然后将异常写入到服务器端的日志(slf4j.Logger)里。
@ResponseBody
@RequestMapping(value = "query", method = RequestMethod.POST)
public ApiResult orderPayQuery(@RequestBody QueryRequest request) throws Exception {
String errorMsg = null;
String outTradeNo = request.getOutTradeNo();
try {
// ......
// 订单业务逻辑处理
orderService.updateOrder(order, queryTradeStatus);
// ......
} catch (Exception e) {
errorMsg = "订单查询异常:" + e.toString();
}
if(errorMsg != null) {
throw new MessageException(errorMsg);
}
return ApiResult.ok(orderService.getOrderResult(outTradeNo));
}
但是有天在修改支付业务sevice类 orderService.updateOrder(order, queryTradeStatus) 方法后,运行时抛出一个空指针异常,但是在服务端的error.log日志里却没有找到service类的抛出异常的具体代码行的信息。
异常日志如下:显示最抛出异常是line 437,也就是“throw new MessageException(errorMsg);” 所在的代码行,具体的异常信息:订单查询异常:java.lang.NullPointerException。这个看不到Service类的业务逻辑方法抛出异常的具体是哪一行代码。
分析下来,是因为抛出的空指针异常被try-catch后,获取到的是 e.toString()。源代码如下:
/**
* Returns a short description of this throwable.
* The result is the concatenation of:
* <ul>
* <li> the {@linkplain Class#getName() name} of the class of this object
* <li> ": " (a colon and a space)
* <li> the result of invoking this object's {@link #getLocalizedMessage}
* method
* </ul>
* If {@code getLocalizedMessage} returns {@code null}, then just
* the class name is returned.
*
* @return a string representation of this throwable.
*/
public String toString() {
String s = getClass().getName();
String message = getLocalizedMessage();
return (message != null) ? (s + ": " + message) : s;
}
这里 toString(),因为 getLocalizedMessage() 为 null, 所以返回的内容是异常类名:java.lang.NullPointerException, 所以要想获取到异常的完整的堆栈信息,应该采用下面的方法:
errorMsg = "订单查询异常:" + stackTrace(e);
/***
* 获取栈信息
* @param exception 异常对象
* @return 栈追踪的完整信息
*/
public static String stackTrace(Exception exception) {
StringWriter stringWriter = new StringWriter();
exception.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
所以,要想获取完整的堆栈信息,不要使用 toString() 方法 ,另外 getMessage() 方法可能获取的也是 null。
另外,spring相关项目大部分情况下都会通过全局捕获异常,并将其包装为更人性化地提示给前端。并且在捕获异常后一般也都会记录到日志里(异常的调用堆栈信息),方便开发排查问题。这里有个比较明显的问题,如果记录全部异常堆栈信息,不但浪费磁盘空间,而且底层框架的堆栈信息对排查问题没有多大指导性意义。如果想继续优化,建议参考下文: