3分钟学会使用AOP完成日志记录

1.相关概念

AOP核心概念
1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)-》(通知+切点)

类是对物体特征的抽象,切面就是对横切关注点的抽象。
通知+切点
意思就是所有要被应用到增强(advice)代码的地方。(包括方法的方位信息)
3、连接点(joinpoint)-》(被拦截的方法)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)-》(描述拦截那些方法的部分)

对连接点进行拦截的定义
5、通知(advice)-》(拦截后执行自己业务逻辑的那些部分)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
这玩意也叫 增强
在逻辑层次上包括了我们抽取的公共逻辑和方位信息。因为Spring只能方法级别的应用AOP,也就是我们常见的before,after,after-returning,after-throwing,around五种,意思就是在方法调用前后,异常时候执行我这段公共逻辑呗。
6、目标对象

代理的目标对象
7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程。
比如根据Advice中的方位信息在指定切点的方法前后,执行增强。这个过程Spring 替我们做好了。利用的是CGLIB动态代理技术。
8、引入(introduction)

关于这些概念,不清楚的同学可以多看看Spring-AOP相关博客或者书籍.

2.自定义日志注解

自定义日志注解,可以更加控制我们需要落库的请求日志以及方法.


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface YuanLiLog {
     String name();//所调用接口的名称
     /**
      * 这里我们可以根据自己的需要定制很多属性
      * 比如 intoDb :标识该条操作日志是否需要持久化存储
      */
    // boolean intoDb() default false;
}

3.完成切面逻辑


@Aspect
@Component
@Slf4j
public class WebLogAspect {
    private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();

    /**
     * 横切点
     */
    @Pointcut("execution(public  * com.yuanfudao.risk.server.rpc..*.*(..))")
    public void webLog() {
    }
    /**
     * 接收请求,并记录数据
     * @param joinPoint
     * @param yuanLiLog
     */
    @Before(value = "@annotation(yuanLiLog)")
    public void doBefore(JoinPoint joinPoint, YuanLiLog yuanLiLog) {
        // 接收到请求
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        YuanliLogDoBuilder yuanliLogDoBuilder = YuanliLogDo.builder()
                .ip(request.getRemoteAddr())
                .url(request.getRequestURL().toString())
                .classMethod(joinPoint.getSignature().getDeclaringTypeName() + "."
                        + joinPoint.getSignature().getName())
                .method(yuanLiLog.name());
        log.info("WebLogAspect doBefore request: {}",RiskJsonUtil.toJson(joinPoint.getArgs()));
        if (joinPoint.getArgs()!=null&&joinPoint.getArgs().length>0){
            RiskRequest riskRequest = RiskJsonUtil.toEntity(RiskJsonUtil.toJson(joinPoint.getArgs()[0]), RiskRequest.class);
            yuanliLogDoBuilder.requestParam(RiskJsonUtil.toJson(joinPoint.getArgs()[0]))
                                .bizType(riskRequest.getBizType())
                                .bizCode(riskRequest.getBizCode())
                                .requestNo(riskRequest.getRequestNo()==null? UUID.randomUUID().toString():riskRequest.getRequestNo());
        }
        YuanliLogDo yuanliLogDo = yuanliLogDoBuilder.build();
        //todo 比如这里我们可以进行落库
       // Long id = yuanLiLogService.save(TableNameConstant.T_YUANLI_LOG, yuanliLogDo);

        Map<String, Object> threadInfo = new HashMap<>();
       // threadInfo.put("logId",id);
        threadInfo.put("startTime",System.currentTimeMillis());
        threadLocal.set(threadInfo);
    }
    /**
     * 执行成功后处理
     * @param yuanLiLog
     * @param ret 响应结果
     * @throws Throwable
     */
    @AfterReturning(value = "@annotation(yuanLiLog)", returning = "ret")
    public void doAfterReturning(YuanLiLog yuanLiLog, Object ret) throws Throwable {


        Map<String, Object> threadInfo = threadLocal.get();

        Long excuteTime = System.currentTimeMillis()-new Long(threadInfo.get("startTime")+"");
        //TODO 比如这里我们可以获取执行时间  响应结果等等
       /* String updateSql = yuanLiLogService.buildUpdateSql(YuanLiLogQuery.RETURN_PARAM, RiskJsonUtil.toJson(ret), YuanLiLogQuery.EXCUTE_TIME, excuteTime);
        String whereSql =YuanLiLogQuery.ID+SqlKeyConstant.EQ+threadInfo.get("logId");
        yuanLiLogService.update(TableNameConstant.T_YUANLI_LOG,updateSql,whereSql);*/
        log.info("RESPONSE : " + ret);
    }
    /**
     * 获取执行时间
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
   // @Around(value = "@annotation(yuanLiLog)")
    /*public Object doAround(ProceedingJoinPoint proceedingJoinPoint,YuanLiLog yuanLiLog) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object ob = proceedingJoinPoint.proceed();
        Map<String, Object> threadInfo = threadLocal.get();
        Long takeTime = System.currentTimeMillis() - startTime;
       // threadInfo.put("takeTime", takeTime);
        log.info("耗时:" + takeTime);
       // threadLocal.set(threadInfo);
        return ob;
    }*/
    /**
     * 异常处理
     * @param throwable
     */
    @AfterThrowing(value = "webLog()", throwing = "throwable")
    public void doAfterThrowing(Throwable throwable) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();

        ServletRequestAttributes sra = (ServletRequestAttributes) ra;

        HttpServletRequest request = sra.getRequest();
        // 异常信息
        log.error("{}接口调用异常,异常信息{}", request.getRequestURI(), throwable);
    }

}

3.使用 @YuanLiLog(name="risk")记录方法请求日志

这里我通过Test请求该方法:

 @Test
    public void testRisk() throws TException {
        RiskRequest riskRequest = new RiskRequest();
        riskRequest.setBizType("BM");
        riskRequest.setBizCode("01");
        String uuid = UUID.randomUUID().toString();
        riskRequest.setRequestNo(uuid);

        VideoAuditRequest videoAuditRequest = VideoAuditRequest.builder()
                .videoId(115122121121L)
                .channel("banmapai")
                .videoUrl("https://xxxxx.mp4")
                .build();
        Map<String, String> stringStringMap = JsonUtils.jsonToMap(RiskJsonUtil.toJson(videoAuditRequest));
        System.out.println(stringStringMap);
        riskRequest.setBizData(stringStringMap);
        riskRpcHandler.risk(riskRequest);
    }

请求成功后,在对应的库中(我在切面中进行了数据落库)可以查询到对应的数据:

 只是简单的通过AOP完成的日志记录,如果服务QPS比较高,时间积累上,这种通过mysql去记录数据并不合适,可以去参考一些分布式的日志框架.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值