使用aop切面记录程序接口异常日志信息,包含完整方法名,参数类型,参数名,参数值及接口信息,异常信息

我后期会将记录的异常信息写成程序自动根据记录的异常日志重新调用接口执行,目前还没时间写这个功能。(已写完,详情请参阅异常日志的自动重试,根据数据库记录的异常信息模拟用户当时操作对接口重新调用_影耀子的博客-CSDN博客

首先引入spring的相关依赖包

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.16</version>
        </dependency>
        <!-- 连接,版本你们自己选择 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.connector}</version>
        </dependency>

        <dependency>
            <groupId>net.sourceforge.jtds</groupId>
            <artifactId>jtds</artifactId>
            <version>${sqlserver-jtds}</version>
        </dependency>


        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.boot}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis.plus.boot}</version>
        </dependency>
        <!-- hutool工具类,几乎包含所以通用方法 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.3</version>
        </dependency>
        

其次创建异常日志表,用于记录异常日志信息。

CREATE TABLE `config_exception_log` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '异常日志id',
  `operating` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作人',
  `operating_id` int unsigned DEFAULT NULL COMMENT '操作人id',
  `parameter` json DEFAULT (json_object()) COMMENT '操作的参数',
  `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'IP地址',
  `create_date` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `throwable_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '异常信息',
  `source` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '来源',
  `source_url` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '请求接口前页面的浏览器地址',
  `api` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '调用的接口信息',
  `api_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '请求的接口类型',
  `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '执行的方法',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='异常日志表'

 相关mapper层,service层,控制层代码略,这个不需要我发了吧,自己创建或用mybatisplus创建去

注意注意注意:若使用了环绕通知并且环绕通知方法中自己捕获了异常(其他情况不需要添加),需在捕获的catch中添加记录异常日志的代码,比如下面的例子

/**
     * 环绕通知
     * @param jp
     */
    @Around("pointCutServer()")
    public Object doAround(ProceedingJoinPoint jp) {
        Object proceed = R.ERROR();
        try {
            
//            System.out.println("执行前");
            proceed = jp.proceed();

//            System.out.println("执行后");

        } catch (Throwable e) {
            // 添加异常日志
            // 注意,此环绕通知的异常捕捉的话将不会执行AddExceptionLog切面类的异常通知
            ExceptionLog log = ResolveExceptionUtil.exceptionQuery(jp, e);
            if (log != null){
                exceptionLogService.save(log);
            }
            e.printStackTrace();
        }

        return proceed;
    }

添加异常日志的切面类如下

/**
 * @author 影耀子(YingYew)
 * @Description: 添加异常日志类
 * @ClassName: AddExceptionLog
 * @date 2022/8/2 14:08
 */
@Component
@Aspect
public class AddExceptionLog {

    @Autowired(required = false)
    private IExceptionLogService exceptionLogService;

    /**
     * 作用整个项目的接口
     */
    @Pointcut("execution(* com.lookvin.admin.*.controller*.*.*(..))")
    public void pointCutServer(){}

    /**
     * 异常通知
     * @param jp
     */
    @AfterThrowing(value = "pointCutServer()",throwing = "throwable")
    public void doAfterThrowing(JoinPoint jp, Throwable throwable) {

        try {
            ExceptionLog log = ResolveExceptionUtil.exceptionQuery(jp, throwable);
            if (log != null){
                exceptionLogService.save(log);
            }

        } catch (Exception e) {

            e.printStackTrace();
        }
    }

}

 ResolveExceptionUtil类中代码如下,这个是封装的构造异常日志信息工具类

/**
 * @author 影耀子(YingYew)
 * @Description: 解析异常的工具类
 * @Package: com.lookvin.common.exception
 * @ClassName: ResolveException
 * @date 2022/8/2 14:31
 */
public class ResolveExceptionUtil {

    /**
     * 构造异常日志
     * @param request
     * @param throwable 异常信息
     * @param parameter 参数
     * @param methodClass 方法的类名+方法名
     * @return
     */
    public static ExceptionLog buildExceptionLog(HttpServletRequest request, Throwable throwable, JSONObject parameter,String methodClass){
        ExceptionLog log = new ExceptionLog();
        if (request != null){
            // 这个是jwt解密后的信息,用于记录操作者的信息,你也可以改成你自己记录操作者的业务逻辑
            Object userClaims = request.getAttribute("user_claims");
            if (userClaims != null){
                Claims claims = (Claims) userClaims;
                log.setOperating(claims.getSubject());
                log.setOperatingId(Integer.valueOf(claims.getId()));
            }
            // 这个是获取ip地址
            log.setIp(ServletUtil.getClientIP(request));
            log.setThrowableText(ExceptionUtil.stacktraceToString(throwable));
            // 这个是获取此异常的来源,你可以去掉或者根据自己的业务设置来源
            log.setSource(ReqSoure.getSoure(request));
            // 这个是记录调用接口时浏览器的url地址
            log.setSourceUrl(request.getHeader("RefererUrl"));
            log.setApi(request.getRequestURL().toString());
            log.setApiType(request.getMethod());
        }

        log.setParameter(parameter.toString());
        log.setMethod(methodClass);
        return log;
    }


    /**
     * 获取添加异常所属条件并构造异常日志
     * @param jp
     * @param throwable
     * @return
     */
    public static ExceptionLog exceptionQuery(JoinPoint jp, Throwable throwable){

        // CommonException.class这个是我自定义的异常,不需要管,若你没有使用自定义异常,去掉这个判断即可
        if (throwable.getClass() == CommonException.class){
            return null;
        }
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;
        if (requestAttributes != null){
            request = (HttpServletRequest) requestAttributes.
                    resolveReference(RequestAttributes.REFERENCE_REQUEST);
        }

        String methodAllName = jp.getTarget().getClass().getName()+"."+jp.getSignature().getName();
        Object[] args = jp.getArgs();//获取访问的方法的参数
        MethodSignature methodSignature = ((MethodSignature) jp.getSignature());
        String[] parameterNames = methodSignature.getParameterNames();
        Class[] parameterTypes = methodSignature.getParameterTypes();
        JSONObject parameters = new JSONObject();
        JSONArray parameterArray = new JSONArray();
        for (int i = 0; i < args.length; i++) {
            String key = parameterTypes[i].getName()+"."+parameterNames[i];
            parameterArray.put(key);
            parameters.set(key,args[i]);
//            parameters.set(parameterNames[i],args[i]);
        }
        parameters.set(getParameterArrayKey(),parameterArray);
        return buildExceptionLog(request, throwable, parameters, methodAllName);
    }

    /**
     * 获取存储的方法参数集合的key
     * @return
     */
    public static String getParameterArrayKey(){
        return "方法参数列表";
    }
}

文章到这里已经结束了,具体接口测试就不发了,你们可以自己去测试看看,最后我把数据库中记录的异常信息可以给你们看下,前面两条是还没写完时测试的,所以没有相关信息

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值