切面实现Dubbo请求日志以及全局异常处理_dubbo 切面_Rael_liu的博客-程序员秘密

技术标签: spring  java  aop  工作总结  log4j  

一、背景

项目中经常会看到在所有的Rpc接口实现中记录请求参数,以及try catch,每个方法都来一次,看着就不舒服,类似下面这段代码

public CloudServerResponse<Boolean> updateExpressInfo(SendAppraisalExpressParam sendAppraisalExpressParam) {
    

        log.info("updateExpressInfo:{}",JSON.toJSONString(sendAppraisalExpressParam));
        CloudServerResponse<Boolean> cloudServerResponse = new CloudServerResponse<>();
        try{
    
            int i = this.updateExpress(sendAppraisalExpressParam);
            return cloudServerResponse.setResult(i>=1);
        }catch (Exception e){
    
            log.error("AppraisalLogisticService.updateExpressInfo:{}",e);
            return cloudServerResponse;
        }

    }

二、实现

2.1 需要日志记录的东西

  • 请求的服务签名
  • 请求入参
  • 请求结果
  • 请求时间
  • 如果发生了异常,则把异常的返回结果封装一下,记录异常信息

2.2 精简后的代码

    public CloudServerResponse<Boolean> updateExpressInfo(SendAppraisalExpressParam sendAppraisalExpressParam) {
    
        CloudServerResponse<Boolean> cloudServerResponse = new CloudServerResponse<>();
        int i = this.updateExpress(sendAppraisalExpressParam);
        return cloudServerResponse.setResult(i>=1);
    }

是不是清爽多了:)

2.3 实现代码

@Aspect
@Slf4j
@Configuration
@ConditionalOnProperty(name = "wwdz.rpc.service.log.enabled")
public class DubboServiceAspect {
    
    /**
     * 返回值类型为Response的Service
     */
    @Pointcut("@within(org.apache.dubbo.config.annotation.Service) && execution(public com.wwdz.mall.common.vo.response.CloudServerResponse com.wwdz..*.*(..))")
    private void servicePointcut() {
    
    }

    /**
     * 任何持有@Transactional注解的方法
     */
    @Pointcut(value = "@annotation(org.springframework.transaction.annotation.Transactional)")
    private void transactionalPointcut() {
    
    }

    /**
     * 异常处理切面
     * 将异常包装为Response,避免dubbo进行包装
     *
     * @param pjp 处理点
     * @return Object
     */
    @Around("servicePointcut() && !transactionalPointcut()")
    public Object doAround(ProceedingJoinPoint pjp) {
    
        Object[] args = pjp.getArgs();
        Long currentTimeMillis = System.currentTimeMillis();
        try {
    
            Object result = pjp.proceed();
            processSuccessfulLog(pjp, args, result, currentTimeMillis);
            return result;
        } catch (BizException e) {
     // 业务自定义异常
            CloudServerResponse cloudServerResponse = e.createResponse();
            processSuccessfulLog(pjp, args, cloudServerResponse, currentTimeMillis);
            return cloudServerResponse;
        }
        catch (Throwable e) {
    
            processException(pjp, args, e, currentTimeMillis);
            return CloudResultCode.INTERAL_ERROR.createResponse();
        }
    }

    /**
     * 记录请求成功日志
     * @param joinPoint
     * @param args
     * @param result
     * @param startCurrentTimeMillis
     */
    private void processSuccessfulLog(ProceedingJoinPoint joinPoint, Object[] args, Object result, Long startCurrentTimeMillis) {
    
        MethodSignature methodSign = (MethodSignature)joinPoint.getSignature();
        UnLog unLog = methodSign.getMethod().getAnnotation(UnLog.class);
        StringBuilder logMsg = new StringBuilder();
        logMsg.append(logServiceMsg(joinPoint));
        logMsg.append(logArgs(args));
        logMsg.append(logResponse(result));
        logMsg.append(logTimeMillis(startCurrentTimeMillis));
        if(unLog == null){
    
            log.info(logMsg.toString());
        }
    }

    /**
     * 任何持有@Transactional注解的方法异常处理切面
     * 将自定义的业务异常转为RuntimeException:
     * 1.规避dubbo的包装,让customer可以正常获取message
     * 2.抛出RuntimeException使事务可以正确回滚
     * 其他异常不处理
     *
     * @param pjp 处理点
     * @return Object
     */
    @Around("servicePointcut() && transactionalPointcut()")
    public Object doTransactionalAround(ProceedingJoinPoint pjp) throws Throwable {
    
        try {
    
            return pjp.proceed();
        } catch (Throwable e) {
    
            // dubbo会将异常捕获进行打印,这里就不打印了
            // processException(pjp, args, e);
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 处理异常
     *  @param joinPoint 切点
     * @param args      参数
     * @param throwable 异常
     * @param startCurrentTimeMillis
     */
    private void processException(final ProceedingJoinPoint joinPoint, final Object[] args, Throwable throwable, Long startCurrentTimeMillis) {
    
        StringBuilder logMsg = new StringBuilder();
        logMsg.append(logServiceMsg(joinPoint));
        logMsg.append(logArgs(args));
        logMsg.append(logTimeMillis(startCurrentTimeMillis));
        log.error(logMsg.toString(), throwable);
    }

    /**
     * 拼接入参
     * @param args
     * @return
     */
    private String logArgs(Object[] args){
    
        if(args == null || args.length <= 0){
    
            return "Request: No request param\n";
        }
        List<Object> usableArgs = new ArrayList<>();
        Collections.addAll(usableArgs, args);
        return "Request: " + JSONObject.toJSONString(usableArgs) + "\n";
    }

    /**
     * 拼接出参
     * @param retVal
     * @return
     */
    private String logResponse(Object retVal) {
    
        return "Response: " + JSONObject.toJSONString(retVal) + "\n";
    }

    /**
     * 拼接服务接口信息
     * @param joinPoint
     * @return
     */
    private String logServiceMsg(final ProceedingJoinPoint joinPoint){
    
        return "Service: " + joinPoint.toLongString() + "\n";
    }

    /**
     * 拼接执行时间
     * @param startCurrentTimeMillis
     * @return
     */
    private String logTimeMillis(long startCurrentTimeMillis){
    
        return "TimeMillis: " + (System.currentTimeMillis() - startCurrentTimeMillis) + "\n";
    }
}

以上代码注解应该挺清楚,代码也比较简单,使用的时候注意要在配置中添加一下,默认是不开启的

wwdz.rpc.service.log.enabled = true

还有一点要注意,假如有一些请求入参或者请求结果很大,日志文件可能会暴涨,这种情况下如果确定这个日志没有太大的价值,可以在方法上加@Unlog注解,屏蔽这个方法的日志

/**
 * 注解加上后不打请求日志
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UnLog {
    
}

执行一下,看看日志打印的结果:
在这里插入图片描述
服务名service
入参request
结果response
耗时TimeMillis
都打出来了

异常日志
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xue632777974/article/details/106102718

智能推荐

递归获取JSON内容的key-value值_积跬步以至千里的博客-程序员秘密

方法主体: /** * 递归获取每个JSONObject的最后一个节点的key与value * 如果遇到相等的key,则key为'key=>value'执行时保存 。或在后一个key值拼接随机种子 * 为以后处理JSON的主要工具类 * 开发日期:2014-7-10 16:41 * * @author mengfeiyang * @param o

linux c语言 printf,LinuxC下wprintf和printf混用_魏长卿的博客-程序员秘密

译文部分Linux C下printf 和wprintf混用,仅一个有效果,另一个写返回-1.无errno信息,但是写失败。问题解答链接https://stackoverflow.com/questions/26816547/whats-the-difference-between-printfs-printfls-wprintfs-and-wp标准解析链接https://en.cppreferen...

SpringBoot--SpringBoot项目结构介绍_我和井盖都笑了的博客-程序员秘密

Spring Boot 项目结构介绍&nbsp;&nbsp;&nbsp; 1 POM 文件&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.1 继承&lt;parent&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-start...

Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.21.0:test (default-test) on_路途芬芳的博客-程序员秘密

今天在打包springboot项目的时候遇到该报错解决1:在pom加上&lt;build&gt; &lt;plugins&gt; &lt;plugin&gt; &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt; &lt;artifactId&gt;maven-s...

单例模式(Singleton)_weixin_33716941的博客-程序员秘密

为什么80%的码农都做不了架构师?&gt;&gt;&gt; ...

随便推点

2、机器学习中的调参:随机搜索和网格搜索_steelDK的博客-程序员秘密

机器学习中的调参前言1、随机搜索和网格搜索2、 遗传算法前言超参数调优是机器学习中的重要一环,拿随机森林算法而言,树的个数,数的深度,剪枝参数等等需要找到最优的参数组合,超参数较少时,我们可以采用for循环遍历所有参数的可能组合,但参数很多时,最优参数的搜寻将会变得困难,本文介绍了几种常用的调参方法,后续如果学到还会更新其他调参算法。其中网格搜索法和随机搜索法采用的是sklearn中的GridSearchCV类和RandomizedSearchCV类,所用实例的数据集点击这下载,数据集为爱荷华州住房数据

构建docker镜像时fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz超时_yanwei2020的博客-程序员秘密

1、超时原因外部网站,国内访问时可能会超时fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz2、解决方法修改Dockerfile,使用国内的alpine源2.1、正确的做法正确的做法是使用国内源完全覆盖 /etc/apk/repositories在Dockerfile中增加下面的第二行FROM alpine:3.7RUN echo -e http://mirrors.ustc.edu.cn

第2章 Linux&Unix_会思考的浣熊的博客-程序员秘密

第2章 Linux&Unixbooting bootstrappingmonolithic kernelmicrokernel serveGUI graphical user interface非盈利版权 Copyleft GPL general public license 发行版 distribution (dist

【maven】将jar包添加进本地仓库中_dfile jar__修铁路的的博客-程序员秘密

开发时,偶尔会遇到需要的jar包,明明maven仓库中心有,但就是载不下来,需要我们从仓库中心,下载后,将jar包添加进本地的仓库中,供项目总的pom文件引用。 1. 从maven仓库中心或者是其他途径下载我们所需要的jar包 2. 在文本编辑器中,提前编辑好以下命令,根据jar包实际情况替换对应的变量mvn install:install-file "-Dfile=本地jar包所在路径" "-DgroupId=设置groupId" "-Darti...

java判断string字节数_Java中关于String类的基本操作(字符与字符串的相互转换、字节与字符串的相互转换、字符串比较、..._夜色冷浮华的博客-程序员秘密

文章目录1 字符与字符串的相互转换1.1 将字符数组转为字符串1.2 将字符串转为字符数组1.3 将字符串转为单个字符1.4 判断一个字符串是否由数字组成?2 字节与字符串的相互转换2.1 将字节数组转为字符串2.2 将字符串转为字节数组3 字符串比较3.1 不区分大小相等比较3.2 比较两个字符串的大小4 字符串查找4.1 判断字符串在源字符串中是否存在4.2 判断字符串是否以指定的字符串开始4...

使用qemu运行X86版本的QNX7.0系统_qemu qnx_玄道公子的博客-程序员秘密

原本想使用VMWare来运行X86版本的QNX7.0系统,但是运行起来之后,没法输入。 虽然QNX官方有提供现成的QNX VMware镜像,但那不是我需要的东西,所以参照官方的QNX VMware镜像,根据它的启动脚本, 我将标准的QNX X86 BSP包中编译和启动脚本x86_64-denverton.build文件进行了修改,针对VMware的特殊情况,使用了devh-ps2ser-vm.s...

推荐文章

热门文章

相关标签