一.日志记录
1.1.正确的定义日志:
传统的使用方式:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final Logger logger = LoggerFactory.getLogger(getClass());
或者使用注解的方式(此处需要安装lombok插件):
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test{
}
1.2.正确的记录日志:
使用参数化形式{}占位,[] 进行参数隔离
logger.info("当前城市间交通费费用标准席别的控制方式是 [{}] ", moneyCtrlType);
阿里大厂的建议说明:String 字符串的拼接会使用 StringBuilder 的append()方式,有一定的性能损耗。使用这种方式的打印[]里面是输出的动态参数,{}用来占位类似绑定变量,使用占位符 仅是替换动作,可以有效提升性能,并且可读性好。
1.3.正确的异常日志记录:
logger.error(XXX方法出现异常:+ e.getMessage(), e);
错误的处理方式:
3.1 :不要使用 System.out.print
System.out.print 只会把日志打印在控制台,不会输出到日志中,不利于后期管理查看。
3.2 :不要使用 e.printStackTrace()
printStackTrace的源码:
public void printStackTrace() {
printStackTrace(System.err);
}
e.printStackTrace()其实也是通过System.err把日志输出到控制台,不会输出到日志中,不利于后期管理查看。
3.3 :没有输出全部异常信息
log.error("系统发生异常:",e.getMessage());
ex.getMessage() 只会记录错误基本描述信息,不会记录详细的堆栈异常信息,不利于排查问题。
3.4 : 不要使用错误的日志级别:(error信息用info进行记录)
log.info("系统发生异常:",e);
二.异常处理
2.1 :异常的背景:
传统的异常处理,我们是在控制层加了许多的try{}catch(){}代码,分别对自定义异常,RunTimeException 以及其他情况做处理,代码类似如下所示:
@ApiOperation(value = "获取当前用户所在的区划信息", notes = "返回结果:查询成功 200/查询失败 500")
@RequestMapping(value = "/getTestException", method = {RequestMethod.GET})
@ResponseBody
public ArRes getTestException(){
try {
arBasicAreaLevelService.getTestException();
return ArRes.ok(ArStatus.QUERY_SUCCESS.getMsg());
}catch (ArException e) {
return ArRes.error(e.getMessage());
}catch (Exception e) {
logger.error("getTestException error:" + e.getMessage(), e);
return ArRes.error(ArStatus.QUERY_FAIL.getMsg());
}
}
但是像每个控制层都包含有这些代码,这些代码都是相同的,是不是可以对这些代码做统一处理呢?这样简化后的代码如下所示,是不是整洁了很多:
@ApiOperation(value = "获取当前用户所在的区划信息", notes = "返回结果:查询成功 200/查询失败 500")
@RequestMapping(value = "/getTestException", method = {RequestMethod.GET})
@ResponseBody
public ArRes getTestException(){
arBasicAreaLevelService.getTestException();
return ArRes.ok(ArStatus.QUERY_SUCCESS.getMsg());
}
2.2 :统一异常处理:
以下是返回结果的处理,采用继承HashMap的方式可以控制随意增加参数:
import org.apache.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
public class ArRes extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public ArRes() {
put("code", 200);
put("flag", "SUCCESS");
put("msg", "操作成功");
}
public static ArRes error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR,"ERROR", "未知异常,请联系管理员");
}
public static ArRes error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "ERROR",msg);
}
public static ArRes error(int code,String flag,String msg) {
ArRes r = new ArRes();
r.put("code", code);
r.put("flag", flag);
r.put("msg", msg);
return r;
}
public static ArRes fail(String msg) {
ArRes r = new ArRes();
r.put("code", 200);
r.put("flag", "FAIL");
r.put("msg", msg);
return r;
}
public static ArRes warn(String msg) {
ArRes r = new ArRes();
r.put("code", 200);
r.put("flag", "WARN");
r.put("msg", msg);
return r;
}
public static ArRes ok(String msg) {
ArRes r = new ArRes();
r.put("flag", "SUCCESS");
r.put("msg", msg);
return r;
}
public static ArRes ok(Map<String, Object> map) {
ArRes r = new ArRes();
r.putAll(map);
return r;
}
public static ArRes ok() {
return new ArRes();
}
public ArRes put(String key, Object value) {
super.put(key, value);
return this;
}
public ArRes putData(Object value) {
super.put("data", value);
return this;
}
}
以下是统一异常的代码处理:
package com.ufgov.ar.common.constant;
import com.ufgov.ar.common.util.ArBillException;
import com.ufgov.ar.common.util.ArStringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.io.IOException;
/**
* 异常处理拦截器
*
* @author hll
* @date 2018年5月25日
*/
@CrossOrigin
@RestControllerAdvice
@Slf4j
@ResponseBody
public class GlobalExceptionHandler {
//运行时异常
@ExceptionHandler(RuntimeException.class)
public ArRes runtimeExceptionHandler(RuntimeException ex) {
return exceptionFormat(1, ex);
}
//空指针异常
@ExceptionHandler(NullPointerException.class)
public ArRes nullPointerExceptionHandler(NullPointerException ex) {
return exceptionFormat(2, ex);
}
//类型转换异常
@ExceptionHandler(ClassCastException.class)
public ArRes classCastExceptionHandler(ClassCastException ex) {
return exceptionFormat(3, ex);
}
//IO异常
@ExceptionHandler(IOException.class)
public ArRes iOExceptionHandler(IOException ex) {
return exceptionFormat(4, ex);
}
//未知方法异常
@ExceptionHandler(NoSuchMethodException.class)
public ArRes noSuchMethodExceptionHandler(NoSuchMethodException ex) {
return exceptionFormat(5, ex);
}
//数组越界异常
@ExceptionHandler(IndexOutOfBoundsException.class)
public ArRes indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
return exceptionFormat(6, ex);
}
//400错误
@ExceptionHandler({HttpMessageNotReadableException.class})
public ArRes requestNotReadable(HttpMessageNotReadableException ex) {
return exceptionFormat(7, ex);
}
//400错误
@ExceptionHandler({TypeMismatchException.class})
public ArRes requestTypeMismatch(TypeMismatchException ex) {
return exceptionFormat(8, ex);
}
//400错误
@ExceptionHandler({MissingServletRequestParameterException.class})
public ArRes requestMissingServletRequest(MissingServletRequestParameterException ex) {
return exceptionFormat(9, ex);
}
//405错误
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public ArRes request405(HttpRequestMethodNotSupportedException ex) {
return exceptionFormat(10, ex);
}
//406错误
@ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
public ArRes request406(HttpMediaTypeNotAcceptableException ex) {
return exceptionFormat(11, ex);
}
//500错误
@ExceptionHandler({ConversionNotSupportedException.class, HttpMessageNotWritableException.class})
public ArRes server500(RuntimeException ex) {
return exceptionFormat(12, ex);
}
//栈溢出
@ExceptionHandler({StackOverflowError.class})
public ArRes requestStackOverflow(StackOverflowError ex) {
return exceptionFormat(13, ex);
}
//其他错误
@ExceptionHandler({Exception.class})
public ArRes exception(Exception ex) {
return exceptionFormat(14, ex);
}
//自定义异常捕获
@ExceptionHandler({ArException.class})
public ArRes myException(ArException ex) {
return exceptionFormat(999, ex);
}
//自定义异常捕获
@ExceptionHandler({ArBillException.class})
public ArRes arBillException(ArBillException ex) {
return exceptionFormat(999, ex);
}
private <T extends Throwable> ArRes exceptionFormat(Integer code, T ex) {
String errorMessage = "";
//自定义异常不需要截取处理了
if (code != 999) {
log.error("系统发生异常:", ex);
errorMessage = getThrowableCauseMessage(ex.getCause());
errorMessage = errorMessage + getStackTraceElementMessage(ex);
}
if (ArStringUtil.isNotEmpty(errorMessage)) {
//errorMessage 不为空 说明是一些系统异常 或者RunTimeException,所以此处ERROR做友好提示处理。
return ArRes.error(code, "ERROR", "系统异常,请重试!").put("errorMessage", errorMessage);
}
return ArRes.error(code, "ERROR", ex.getMessage());
}
/**
* @Author tianmengwei
* @Description 获取Throwable 的Message信息
* @param
* @Return java.lang.String
* @Exception
* @Date 2019/12/6 10:30
*/
private String getThrowableCauseMessage(Throwable cause){
if(cause == null){
return "";
}
String message = "";
while (cause != null) {
message = cause.getMessage();
cause = cause.getCause();
}
return message;
}
/**
* @Author tianmengwei
* @Description 获取 StackTraceElementMessage 信息,此处规则是根据包名和 类名规则处理,并且限定为三次,符合太多也不行
* @param
* @Return java.lang.String
* @Exception
* @Date 2019/12/6 10:32
*/
private String getStackTraceElementMessage(Throwable ex){
int index = 0;
String errorMessage = "";
StackTraceElement[] stackTraceElements = ex.getStackTrace();
StringBuilder sb = new StringBuilder();
sb.append(ex.fillInStackTrace()+" ");
for(StackTraceElement stackTraceElement:stackTraceElements){
if(index >3){
return errorMessage;
}
String className = stackTraceElement.getClassName();
if(className.contains("com.ufgov.ar") && className.contains("ServiceImpl")){
index ++;
sb.append(stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + "(" + stackTraceElement.getFileName() + ":"
+ stackTraceElement.getLineNumber() + ") ");
errorMessage = sb.toString();
}
}
return errorMessage;
}
}
2.3 : e.getStackTrace()和e.printStackTrace()区别:
e.getStackTrace()返回的是通过getOurStackTrace方法获取的StackTraceElement[]数组,而这个StackTraceElement是ERROR的每一个cause by的信息,上述代码我将数组的第一条数据返回,会将代码关键信息显示出来,便于调试,效果如下:
eprintStackTrace()方法直接打印输出到控制台