异常处理(正确思路)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 异常处理(正确思路)

一、不同业务层处理异常的思路

Repository 层出现异常或许可以忽略,或许可以降级,或许需要转化为一个友好的异常。如果一律捕获异常仅记录日志,很可能业务逻辑已经出错,而用户和程序本身完全感知不到

Service 层往往涉及数据库事务,出现异常同样不适合捕获,否则事务无法自动回滚。此外 Service 层涉及业务逻辑,有些业务逻辑执行中遇到业务异常,可能需要在异常后转入分支业务流程。如果业务异常都被框架捕获了,业务功能就会不正常

如果下层异常上升到 Controller 层还是无法处理的话。Controller 层往往会给予用户友好提示,或是根据每一个 API 的异常表返回指定的异常类型,同样无法对所有异常一视同仁

二、如何定义合格的统一异常处理

       对于自定义的业务异常,以 Warn 级别的日志记录异常以及当前 URL、执行方法等信息后,提取异常中的错误码和消息等信息,转换为合适的 API 包装体返回给 API 调用方;对于无法处理的系统异常,以 Error 级别的日志记录异常和上下文信息(比如 URL、参数、用户 ID)后,转换为普适的“服务器忙,请稍后再试”异常信息,同样以 API 包装体返回给调用方

@RestControllerAdvice
@Slf4j
public class RestControllerExceptionHandler {
    private static int GENERIC_SERVER_ERROR_CODE = 2000;
    private static String GENERIC_SERVER_ERROR_MESSAGE = "服务器忙,请稍后再试";
    @ExceptionHandler
    public APIResponse handle(HttpServletRequest req, HandlerMethod method, Exception ex) {
        if (ex instanceof BusinessException) {
            BusinessException exception = (BusinessException) ex;
            log.warn(String.format("访问 %s -> %s 出现业务异常!", req.getRequestURI(), method.toString()), ex);
            return new APIResponse(false, null, exception.getCode(), exception.getMessage());
        } else {
            log.error(String.format("访问 %s -> %s 出现系统异常!", req.getRequestURI(), method.toString()), ex);
            return new APIResponse(false, null, GENERIC_SERVER_ERROR_CODE, GENERIC_SERVER_ERROR_MESSAGE);
        }
    }
}

三、 其他注意点

禁止生吞异常

生吞异常也就是直接丢弃异常不记录、不抛出。不管是你认为多么不重要的异常,都不应该生吞,哪怕是一个日志也好,因为被生吞掉的异常一旦导致Bug就很难在程序中找到蛛丝马迹,使得 Bug 排查工作难上加难

禁止丢弃异常的原始信息

丢弃了异常的原始信息容易让我们无法精准锁定异常

比如:

    private void readFile() throws IOException {
        Files.readAllLines(Paths.get("a_file"));
    }
    @GetMapping("wrong1")
    public void wrong1(){
        try {
            readFile();
        } catch (IOException e) {            
            //只保留了异常消息,栈没有记录
            log.error("文件读取错误, {}", e.getMessage());
            //原始异常信息丢失
            throw new RuntimeException("系统忙请稍后再试");
        }
    }

我们的日志就成了这个样子,谁能说这个是什么错吗?

[12:57:19.746] [http-nio-45678-exec-1] [ERROR] [.g.t.c.e.d.HandleExceptionController:35 ] - 文件读取错误 , a_file

通常我们建议采用下面的方式记录异常信息

catch (IOException e) {
    log.error("文件读取错误", e);
    throw new RuntimeException("系统忙请稍后再试");
}

禁止抛出异常时不指定任何消息

throw new RuntimeException();

[13:25:18.031] [http-nio-45678-exec-3] [ERROR] [c.e.d.RestControllerExceptionHandler:24 ] - 访问 /handleexception/wrong3 -> org…..demo1.HandleExceptionController#wrong3(String) 出现系统异常!

java.lang.RuntimeException: null

...

这里的 null 非常容易引起误解。按照空指针问题排查半天才发现,其实是异常的 message 为空  

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
7月前
|
程序员 编译器 C++
C++中的异常处理:技术详解与实践
C++中的异常处理:技术详解与实践
123 1
|
7月前
|
安全 编译器 C++
C++从入门到精通:3.2异常处理——掌握C++的异常处理机制,提高程序健壮性
C++从入门到精通:3.2异常处理——掌握C++的异常处理机制,提高程序健壮性
210 0
一文搞懂Go语言错误处理【异常捕获、异常抛出】
一文搞懂Go语言错误处理【异常捕获、异常抛出】
一文搞懂Go语言错误处理【异常捕获、异常抛出】
|
Java UED
异常处理:让你的代码更加健壮
异常处理:让你的代码更加健壮
117 0
C++学习笔记_10 异常处理 2021-04-27
C++学习笔记_10 异常处理 2021-04-27
|
Linux C++ Windows
【C++知识点】异常处理
【C++知识点】异常处理
157 0
如何用c++实现异常处理
如何用c++实现异常处理
如何用c++实现异常处理
|
存储 Java 编译器
Java异常是如何实现的?
Java异常是如何实现的?
Java异常是如何实现的?
|
SQL 运维 关系型数据库
PDOException 异常处理|学习笔记
快速学习 PDOException 异常处理
PDOException 异常处理|学习笔记
|
缓存 Java 大数据
异常处理|学习笔记
快速学习异常处理。