java如何正确的日志记录和异常处理

4,668 阅读5分钟

一.日志记录

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()方法直接打印输出到控制台