SpringBoot统一异常捕获以及logback最简洁实现

一、背景

在SpringBoot开发应用的时候,程序抛出的异常如果能进行统一的管理和处理,会极大方便我们排查问题,SpringBoot预留了controller响应的扩展点,我们只需要按照对应的方式去自定义自己的实现即可。

@ControllerAdvice表明GlobalExceptionAdvice是一个异常的处理类; @ExceptionHandler表明当前方法即为将要处理错误信息的逻辑代码,通过value来指定异常类型。异常的处理方法需要传入两个参数,一个是HttpServletRequest请求对象,可以从这里获取请求相关的一些信息,比如请求的url或者参数等,另一个是对应自定义的Exception,它就是抛出的异常

二、集成

1.编写异常枚举类

public interface IStatusCode {
    public Integer getCode();
    public String  getMsg();
}


@Getter
public enum HttpCode implements IStatusCode {
    CURRENT_NO_PERMISSION(4001,"当前用户权限不足"),
    INTERNAL_ADDRESS_FORBIDDEN_ACCESS(4003,"内部访问地址,禁止访问"),
    REQUEST_PARAM_ERROR(4000,"请求参数错误"),
    REQUEST_URL_ERROR(4004,"请求URL地址错误"),
    REQUEST_METHOD_ERROR(4005,"请求方法错误"),
    SERVER_INTERNAL_EXEC_ERROR(5000,"服务器内部执行错误"),
    DB_SERVICE_ERROR(5001,"数据库服务错误"),
    INTERNAL_INTERFACE_CALL_ERROR(5002,"内部接口调用错误"),
    EXTERNAL_INTERFACE_CALL_ERROR(5003,"外部接口调用错误"),

    SUCCESS(2000, "请求处理成功");

    private Integer code;
    private String  msg;

    HttpCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

}

2.编写响应类 

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResultDTO {
    private Integer code;
    private String  msg;
    private Object  data;

    public static ResultDTO getInstance(Integer code, String msg, Object object){
        return ResultDTO.builder().code(code).msg(msg).data(object).build();
    }

    public static ResultDTO success(IStatusCode iStatusCode){
        return ResultDTO.builder().code(iStatusCode.getCode()).msg(iStatusCode.getMsg()).build();
    }

    public static ResultDTO success(IStatusCode iStatusCode, Object object){
        return ResultDTO.builder().code(iStatusCode.getCode()).msg(iStatusCode.getMsg()).data(object).build();
    }

    public static ResultDTO error(IStatusCode iStatusCode){
        return ResultDTO.builder().code(iStatusCode.getCode()).msg(iStatusCode.getMsg()).build();
    }

    public static ResultDTO error(IStatusCode iStatusCode, Object object){
        return ResultDTO.builder().code(iStatusCode.getCode()).msg(iStatusCode.getMsg()).data(object).build();
    }

}

3.编写异常处理类

@Data
public class HttpException extends RuntimeException {
    private Integer code;
    private String  msg;

    // 手动设置异常
    public HttpException(IStatusCode iStatusCode, String message){
        // message用于用户设置抛出错误详情,例如:当前价格-5,小于0
        super(message);
        // 状态码
        this.code = iStatusCode.getCode();
        // 状态码配套的msg
        this.msg  = iStatusCode.getMsg();
    }

    // 默认异常使用IAppCode_ERROR状态码
    public HttpException(String message) {
        super(message);
        this.code = HttpCode.SERVER_INTERNAL_EXEC_ERROR.getCode();
        this.msg  = HttpCode.SERVER_INTERNAL_EXEC_ERROR.getMsg();
    }
}




@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {

    /**
     * @Description: 未自定义的异常,统一抛出:业务异常+手动指定异常信息
    */
    @ExceptionHandler({Exception.class})
    public ResultDTO exceptionHandler(Exception e){
        log.error("", e);
        return ResultDTO.error(HttpCode.SERVER_INTERNAL_EXEC_ERROR, e.getMessage());
    }

    /**
     * @Description: 参数校验异常
     */
    @ExceptionHandler({BindException.class})
    public ResultDTO methodParamNotValidExceptionHandler(BindException e){
        log.error("", e);
        String defaultMessage = e.getAllErrors().get(0).getDefaultMessage();
        return ResultDTO.error(HttpCode.REQUEST_PARAM_ERROR, defaultMessage);
    }

    /**
     * @Description: 请求异常
     */
    @ExceptionHandler({HttpException.class})
    public ResultDTO httpExceptionHandler(HttpException e){
        // 打印手动设置的异常信息
        log.error("", e);
        return ResultDTO.getInstance(e.getCode(), e.getMsg(), e.getMessage());
    }

//    /**
//     * @Description: 业务异常
//     */
//    @ExceptionHandler({APIException.class})
//    public ResultDTO apiExceptionHandler(APIException e){
//        log.error("", e);
//        return ResultDTO.getInstance(e.getCode(), e.getMsg(), e.getMessage());
//    }


    /**
     * @Description:  获取异常堆栈信息: log.error(getExceptionDetail(e));
     */
    public String getExceptionDetail(Exception e) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(e.getClass() + System.getProperty("line.separator"));
        stringBuilder.append(e.getLocalizedMessage() + System.getProperty("line.separator"));
        StackTraceElement[] arr = e.getStackTrace();
        for (int i = 0; i < arr.length; i++) {
            stringBuilder.append(arr[i].toString() + System.getProperty("line.separator"));
        }
        return stringBuilder.toString();
    }

}

4.编写测试类测试

@Slf4j
@RestController
@RequestMapping("/exception")
public class GlobalExceptionController {

    @PostMapping("/validParam")
    public ResultDTO findByVo1(@Validated IProductInfoVo vo){
        ProductInfo productInfo = ProductInfo.builder().build();
        BeanUtils.copyProperties(vo, productInfo);
        return ResultDTO.success(HttpCode.SUCCESS, productInfo);
    }

    @PostMapping("/httpException")
    public ResultDTO findByVo2(@Validated IProductInfoVo vo){
        ProductInfo productInfo = ProductInfo.builder().build();
        productInfo = Objects.nonNull(productInfo) ? null : productInfo;
        Objects.requireNonNull(productInfo, "当前检索条件对应的产品信息不存在");
        return ResultDTO.success(HttpCode.SUCCESS, productInfo);
    }

    @PostMapping("/httpException2")
    public ResultDTO findByVo3(@Validated IProductInfoVo vo){
        ProductInfo productInfo = ProductInfo.builder().build();
        BeanUtils.copyProperties(vo, productInfo);
//        if (!Objects.isNull(productInfo)){
            throw new HttpException("当前请求信息不存在,请审核数据库数据"+productInfo.getProductName());
//            throw new HttpException(HttpCode.INTERNAL_INTERFACE_CALL_ERROR, "当前请求信息不存在,请审核数据库数据");
//        }
        return ResultDTO.success(HttpCode.SUCCESS, productInfo);
    }


}

5.resources目录下配置logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 控制台 appender, 几乎是默认的配置 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="UTF-8">
            <!-- 输出的日志文本格式, 其他的 appender 与之相同 -->
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- info 级别的 appender -->
    <appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日志写入的文件名, 可以是相对目录, 也可以是绝对目录, 如果上级目录不存在会自动创建 -->
        <file>./logs/info/log-stack.log</file>
        <!-- 如果是 true, 日志被追加到文件结尾; 如果是 false, 清空现存文件. 默认是true -->
        <append>true</append>
        <!-- 日志级别过滤器, 只打 INFO 级别的日志-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <!-- 下面2个属性表示: 匹配 level 的接受打印, 不匹配的拒绝打印 -->
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <!-- 最常用的滚动策略, 它根据时间来制定滚动策略, 既负责滚动也负责触发滚动 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 设置滚动文件规则, 如果直接使用 %d, 默认格式是 yyyy-MM-dd -->
            <fileNamePattern>./logs/info/log-stack.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保留14天的日志 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!-- 定义日志输出格式 -->
        <encoder charset="UTF-8">
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- error 级别的 appender -->
    <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>./logs/error/log-stack.log</file>
        <append>true</append>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>./logs/error/log-stack.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保留7天的日志 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!-- 定义日志输出格式 -->
        <encoder charset="UTF-8">
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- error 级别的 appender -->
    <appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>./logs/debug/log-stack.log</file>
        <append>true</append>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>./logs/debug/log-stack.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保留7天的日志 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!-- 定义日志输出格式 -->
        <encoder charset="UTF-8">
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %L - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 指定 com.github 下的日志打印级别, appender -->
    <logger name="com.analysis.tool.platform.server" level="debug" additivity="false">
        <appender-ref ref="stdout"/>
        <appender-ref ref="info"/>
        <appender-ref ref="error"/>
        <appender-ref ref="debug"/>

    </logger>


    <root level="info">
        <appender-ref ref="stdout"/>
        <appender-ref ref="info"/>
        <appender-ref ref="error"/>
    </root>

</configuration>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值