美文网首页
Spring Boot 全局异常拦截,并记录日志

Spring Boot 全局异常拦截,并记录日志

作者: rtctt | 来源:发表于2017-08-16 12:41 被阅读0次

    实现代码

    主要功能实现类

    import com.alibaba.fastjson.JSON;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Enumeration;
    
    /**
     * Created by tt on 2017/6/16.
     * 全局异常处理类
     */
    @ControllerAdvice
    public class ExceptionHandlerAdvice {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        @Value("${err.logging.file}")
        private String errFile;
    
        private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        /**
         * 拦截所有的Exception
         */
        @ExceptionHandler(value = {Exception.class})
        public void exception(Exception exception, HttpServletResponse response, HttpServletRequest request) {
            try {
                FileWriter fileWriter = new FileWriter(errFile, true);
                fileWriter.write("========================================================================================================================================\n");
                //记录错误时间
                fileWriter.write(sdf.format(new Date()) + "\n");
                //记录用户设备型号
                fileWriter.write(request.getHeader("User-Agent") + "\n");
    
                String ip = getRemoteAddress(request);
                String requestURL = request.getRequestURL().toString();
                String url = request.getQueryString() == null ? requestURL + "" : (requestURL + "?" + request.getQueryString());
                //记录请求地址
                fileWriter.write(url + "\n");
                //记录请求ip地址
                fileWriter.write(ip + "\n");
                Enumeration paramNames = request.getParameterNames();
                while (paramNames.hasMoreElements()) {
                    String paramName = (String) paramNames.nextElement();
                    String[] paramValues = request.getParameterValues(paramName);
                    if (paramValues.length == 1) {
                        String paramValue = paramValues[0];
                        if (paramValue.length() != 0) {
                            //记录请求参数
                            fileWriter.write("param: " + paramName + " = " + paramValue + "\n");
                        }
                    }
                }
                PrintWriter printWriter = new PrintWriter(fileWriter);
                //把异常信息记录到日志文件中
                exception.printStackTrace(printWriter);
                //关闭writer流
                fileWriter.write("\n");
                fileWriter.close();
                printWriter.close();
            } catch (IOException e) {
                logger.info("I can't open the file " + errFile);
            }
            //控制台打印异常详细信息
            exception.printStackTrace();
            //使用OutputStream流向客户端输出错误信息
            try {
                OutputStream os = response.getOutputStream();
                //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
                response.setHeader("content-type", "text/html;charset=UTF-8");
                ResultData<Object> resultData = new ResultData<>();
                String e = exception.toString();
                // 异常类型
                resultData.setData("异常类型:" + (e.contains(":") ? e.substring(0, e.indexOf(":")) : e));
                resultData.setCode(555);// 异常信息
                resultData.setMsg("异常信息:" + exception.getMessage());
                String data = JSON.toJSONString(resultData);
                //将字符转换成字节数组,指定以UTF-8编码进行转换
                byte[] dataByteArr = data.getBytes("UTF-8");
                //使用OutputStream流向客户端输出字节数组
                os.write(dataByteArr);
                //关闭输出流
                os.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    
        private static String getRemoteAddress(HttpServletRequest request) {
            String ip = request.getHeader("x-forwarded-for");
            if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || ip.equalsIgnoreCase("unknown")) {
                ip = request.getRemoteAddr();
            }
            return ip;
        }
    }
    

    以下为ResultData类,用于返回给客户端的json格式数据

    public class ResultData<T> {
    
        private T data;
    
        private Integer code = 200;
    
        private String msg;
    
        private Boolean success = true;
    
        // 此处省略getter  setter
    }
    

    代码说明

    @ControllerAdvice

    通过@ControllerAdvice注解到类上,我们可以将对于控制器的全局配置放在同一个位置,通过@ExceptionHandler,@InitBinder,@ModelAttribute(本文只提到@ExceptionHandler,另外两个注解读者有兴趣可自行尝试)注解在该类的方法上,这对所有注解了@Controller的类内的注解了@RequestMapping的方法都有效。

    我们现在要用到的是@ExceptionHandler,用于全局处理控制器里的异常。

    @ExceptionHandler(value = {Exception.class})

    这边可以看到@ExceptionHandler注解内我们使用到了value属性,它可以过滤拦截的条件。此处我们拦截所有的Exception。除此之外我们还可以根据自己的需要,定制细化的异常处理,把各种不同的异常类型分开处理。

    @Value

    @Value("${err.logging.file}")

    @Value 用于获取 application.properties 文件的外部配置属性,此示例在外部配置文件内的配置如下,主要用于配置异常处理的日志错误信息保存的路径,方便项目运行动态设置。

    err.logging.file = err.log
    

    其他细节

    OutputStream os = response.getOutputStream();
    //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
    response.setHeader("content-type", "text/html;charset=UTF-8");
    ResultData<Object> resultData = new ResultData<>();
    String e = exception.toString();
    // 异常类型
    resultData.setData("异常类型:" + (e.contains(":") ? e.substring(0, e.indexOf(":")) : e));
    resultData.setCode(555);// 异常信息
    resultData.setMsg("异常信息:" + exception.getMessage());
    String data = JSON.toJSONString(resultData);
    //将字符转换成字节数组,指定以UTF-8编码进行转换
    byte[] dataByteArr = data.getBytes("UTF-8");
    //使用OutputStream流向客户端输出字节数组
    os.write(dataByteArr);
    //关闭输出流
    os.close();
    

    这里用于异常捕获处理日志后,向客户端输出的异常信息,处理后的json输出示例:

    {
      code: 555,
      data: "异常类型:org.springframework.web.bind.MissingServletRequestParameterException",
      msg: "异常信息:Required List parameter 'ids' is not present",
      success: false
    }
    

    这样可以保持客户端请求服务端接口的返回数据格式一致。

    最后附上日志记录信息的结果(具体的记录信息科自行定制,本文仅提供方案)

    屏幕快照 2017-08-16 13.33.29.png

    相关文章

      网友评论

          本文标题:Spring Boot 全局异常拦截,并记录日志

          本文链接:https://www.haomeiwen.com/subject/qjlirxtx.html