前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >通过AOP和自定义注解实现请求日志收集功能

通过AOP和自定义注解实现请求日志收集功能

作者头像
林老师带你学编程
发布2019-05-25 23:43:55
1.5K0
发布2019-05-25 23:43:55
举报
文章被收录于专栏:强仔仔强仔仔

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1434153

概述

今天给大家介绍一下:如何通过AOP和自定义注解实现全局请求日志收集功能。

一般线上程序都会遇到一个问题,如何排查线上bug,因为有时候这种bug是突发性,测试很难复现的出来。所以记录出错那一刻的请求信息,就异常的关键了。

那请求信息,我们该如何记录下来呢,总不能通过log日志一个个记录下来吧,这样工作量大,而且很难扩展。不用着急,今天就教给大家一招,轻轻松松实现日志收集功能。

接下来我们就来看看,我是如何通过:AOP和自定义注解来实现请求日志统一收集功能。

核心流程

流程图如下所示:

我们先在接口上面添加自定义注解,这样每次请求就都会走AOP的处理中心。在处理中心中,我们可以将请求信息存储到数据库中,方便后期排查问题。

自定义注解

我们先来看看自定义注解是如何实现的,代码如下所示:

代码语言:javascript
复制
/**
 * 自定义注解
 *
 * @author linzhiqiang
 * @date 2019/4/26
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestLog {

    /**
     * 请求模块名称
     * @return
     */
    public String module() default "";

    /**
     * 接口详情描述
     * @return
     */
    public String operationDesc() default "";
}

这边我们定义了两个字段,一个是接口的模块名称,另外一个就是接口具体的功能描述。

接口注释

这两个参数需要在接口处传入,现在我们就来看看接口是如何添加注解和传值的,代码如下所示:

代码语言:javascript
复制
 /**
     * request测试专用
     * @return
     */
    @RequestLog(module = "requestTest", operationDesc = "request测试专用")
    @RequestMapping(value = "requestTest", method = RequestMethod.POST)
    public String requestTest(@RequestBody ArticleSubjectDto articleSubjectDto) {
        String result = null;
        try {
            System.out.println("我的是方法");
            result = "请求成功";
        }catch (Exception e){
            logger.error("requestTest查询失败", e);
            return JsonUtils.toJson(ResponseUtils.failInServer(result));
        }
        return JsonUtils.toJson(ResponseUtils.success(result));
    }

我们可以看到,在接口上面添加我们自定义的注解,然后写上对应的参数值就可以了。

AOP处理中心

最后我们来看看最核心的AOP处理中心是如何实现的,代码如下所示:

代码语言:javascript
复制
/**
 * 切面AOP
 * @author linzhiqiang
 */
@Aspect
@Component
public class SystemLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
    private static final String UNKNOWN = "unknown";

    @Autowired
    private LogRepository logRepository;
    /**
     * 1,表示在哪个类的哪个方法进行切入。配置有切入点表达式。
     * 2,对有@SystemLog标记的方法,记录其执行参数及返回结果。
     */
    @Pointcut("execution(* com.minimal..controller..*.*(..))&&@annotation(com.minimal.common.sdk.log.RequestLog)")
    public void controllerAspect() {
    }

    /**
     * 配置controller环绕通知,使用在方法aspect()上注册的切入点
     */
    @Around("controllerAspect()")
    public Object aroundMethod(ProceedingJoinPoint point) throws Throwable {
        if (logger.isDebugEnabled()) {
            logger.info(">>>>>>>>>>>>>>>进入日志切面<<<<<<<<<<<<<<<<");
        }
        // 获取接口的路径地址
        String methodTarget = point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()";
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        OperateLogPO operLogPO = new OperateLogPO();
        operLogPO.setId(UUID.randomUUID().toString());
        operLogPO.setMethod(methodTarget);
        operLogPO.setCreateTime(new Date());
        operLogPO.setIp(getClientIpAddr(request));
        operLogPO.setBrownerNo(getBrownerNo(request));
        operLogPO.setOsNo(getOsNo(request));
        // 获取接口的请求参数
        operLogPO.setParams(JsonUtils.toJson(point.getArgs()));
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        RequestLog log = method.getAnnotation(RequestLog.class);
        String desc = log.operationDesc();
        String module = log.module();
        operLogPO.setModule(module);
        operLogPO.setOperationDesc("模块:" + module + ",操作行为:" + desc);
        logger.info("前置通知>>>>>>>>>>>>>>>操作模块:" + module + ",操作方法:" + methodTarget + ",操作行为:" + desc + "<<<<<<<<<<<<<<<<");
        Object result = null;
        try {
            result = point.proceed();
            // 设置请求结果
            operLogPO.setResult(JsonUtils.toJson(result));
            // 返回通知(操作成功:1,操作失败:2)
            operLogPO.setStatus("1");
        } catch (Throwable e) {
            operLogPO.setStatus("2");
            // 异常通知
            throw new RuntimeException(e);
        } finally {
            // 后置通知
            logger.info("后置通知>>>>>>>>>>>>>>>操作模块:" + module + ",操作方法:" + methodTarget + ",操作行为:" + desc + ",操作结果:" + operLogPO.getStatus() + "!(操作成功:1,操作失败:2)<<<<<<<<<<<<<<<<");
            logRepository.insert(operLogPO);
        }
        return result;
    }

    /**
     * 功能:获取IP地址
     *
     * @param request
     * @return
     */
    public static String getClientIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 功能:获取浏览器版本
     *
     * @return
     */
    public String getBrownerNo(HttpServletRequest request) {
        return getNo(request, new String[]{"MSIE", "FIREFOX", "CHROME", "SAFARI", "OPERA"});
    }

    /**
     * 功能:获取操作系统版本
     *
     * @return
     */
    public String getOsNo(HttpServletRequest request) {
        return getNo(request, new String[]{"WINDOWS NT", "IOS"});
    }

    /**
     * 获取参数
     *
     * @param request
     * @param osNos
     * @return
     */
    public String getNo(HttpServletRequest request, String[] osNos) {
        String userAgent = request.getHeader("user-agent");
        String osNo = "";
        if (userAgent != null) {
            String str = userAgent.toUpperCase();
            for (int i = 0; i < osNos.length; i++) {
                if (str.indexOf(osNos[i]) > 0) {
                    String str1 = str.substring(str.indexOf(osNos[i]));
                    if (str1.indexOf(";") > 0) {
                        osNo = str1.substring(0, str1.indexOf(";"));
                    } else {
                        osNo = osNos[i];
                    }
                    break;
                }
            }
        }
        return osNo;
    }
}

最核心部分就是添加 @Around注解的方法了,这里是切面的处理中心,我们可以将获取的参数和接口的返回值插入到Mongodb中。

结果测试

最后我们来测试一下整个过程是否没正确,我们通过Postman进行post请求,观察Mongodb中是否有请求日志插入,如果数据插入成功且正确,就说明我们的代码是没问题的,下面我们来看测试结果。

Postman请求

Mongodb日志

我们可以看到,已经成功的将数据插入到Mongodb中了,而且数据完整无误。

到这边通过AOP和自定义注解实现请求日志收集功能就介绍完毕了,是不是超级简单呀~

注意点:

  1. 请求日志我们可以只留前面几个月,不然日志数量太大会影响查询性能。
  2. 核心的接口进行日志收集,其它简单的接口可以不用。
  3. 保存的请求信息要尽量详细,方便日后bug排查。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月28日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 核心流程
  • 自定义注解
  • 接口注释
  • AOP处理中心
  • 结果测试
相关产品与服务
云数据库 MongoDB
腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档