美文网首页
AOP记录日志

AOP记录日志

作者: GIT提交不上 | 来源:发表于2020-04-22 16:46 被阅读0次

    一、AOP相关注解

    • 前置通知 -- @Before
    • 后置通知 -- @After
    • 返回通知 -- @AfterReturning
    • 异常通知 -- @AfterThrowing
    • 环绕通知 -- @Around
    • @PointCut -- 公共切入点表达式
    • @Aspect -- 指定切面类
    • JoinPoint -- 作为函数的参数传入切面方法,可以得到目标方法的相关信息

    参考链接

    二、实现效果

      基于Spring Boot框架,使用AOP相关特性记录日志信息。完整代码见:https://github.com/just-right/logaop

    • 将日志信息存入数据库
    • 当请求发生异常时自定义返回的提示信息
    • 将日志信息存入本地日志文件,发生异常时存储完整的堆栈信息
    • 自定义拦截器结合自定义注解,模拟验证请求信息是否有正确的Token参数
    • 配置druid连接池

    三、关键逻辑

    3.1 使用AOP跟踪请求

      @Before获取请求数据,核心代码如下:

    @Before( value = "pointCutMethod()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)       
        RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //请求SessionID
        String sessionID = request.getRequestedSessionId();
        //请求路径
        String requestURI = request.getRequestURI();
        //获取请求参数信息
        Map<String, String[]> parameterMap = request.getParameterMap();
        String jsonStr = JSONObject.toJSONString(parameterMap);
        //获取IP地址
        String ipAddress = IPUtils.getIpAddr(request);
        //获取请求方法
        String method = request.getMethod();
        //访问时间
        LocalDateTime dateTime = LocalDateTime.now();
        ... ...
    }
    

      @doAfter获取返回状态,核心代码如下:

    @After(value = "pointCutMethod()")
    public void doAfter(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = requestAttributes.getResponse();
        HttpServletRequest request = requestAttributes.getRequest();
        LogAopInfo logEntity = (LogAopInfo) request.getAttribute(ILogAspectConst.REQUEST_ATTRIBUTE);
        //返回状态
        int status = response.getStatus();
        //请求结束时间
        LocalDateTime endDateTime = LocalDateTime.now();
        Duration duration = Duration.between(logEntity.getBeginDateTime(),endDateTime);
        //请求耗时
        long spendTimes = duration.getSeconds();
        ... ...
    }
    

      @AfterReturning获取返回数据,核心代码如下:

    @AfterReturning(value = "pointCutMethod()", returning = "keys")
    public void doAfterReturning(JoinPoint joinPoint, Object keys) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //返回数据信息
        String responseInfo =  JSONObject.toJSONString(keys);
        ... ...
    }
    

      @Around可以处理请求的所有阶段,本例主要用于处理错误信息,核心代码如下:

    @Around(value = "pointCutMethod()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) {
        Object object  = null;
        try {
            //执行方法-返回值
            object =  proceedingJoinPoint.proceed();
        } catch (Throwable ex) {
            //打印错误堆栈信息
            this.printErrorStackTraceInfo(ex);
            //设置返回值
            object = ErrorInfoEnum.getErrorByCode(100).getErrorInfo();
        }
        return object;
    }
    
    //打印错误堆栈信息
    private void printErrorStackTraceInfo(Throwable ex){
        if(ex != null){
            StringBuffer buffer = new StringBuffer();
            for (StackTraceElement element:ex.getStackTrace()) {
                buffer.append("\t at "+element.getClassName());
                buffer.append("."+element.getMethodName());
                buffer.append("("+element.getFileName()+":"+element.getLineNumber()+")\n");
            }
            logger.info(ex.getMessage()+"\n"+buffer.toString());
        }
    }
    

    3.2 自定义拦截器过滤请求

      自定义注解@LogAop,标志是否需要过滤请求。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LogAop {
        boolean logFlag() default true;
    }
    

      自定义拦截器LoggerInterceptor ,结合@LogAop注解过滤请求。

    public class LoggerInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            LogAop loginCheck = handlerMethod.getMethod().getAnnotation(LogAop.class);
            if (loginCheck !=null && loginCheck.logFlag()){
                Map<String, String[]> parameterMap =  request.getParameterMap();
                //查看请求参数是否携带token且token信息以hello为开头
                Optional<Boolean> optional =  parameterMap.entrySet().stream()
                        .filter(e-> e.getKey().equals(ILogAspectConst.REQUEST_TOKEN_FIELD)).findFirst()
                        .map(e->e.getValue()[0].startsWith(ILogAspectConst.TOKEN_INFO));
                if(optional.orElse(false)){
                    return true;
                }
                //设置提示信息        
                response.setContentType(ILogAspectConst.CONTENT_TYPE);
                response.getWriter().println(ILogAspectConst.TIP_MSG);
                return false;
            }
            return true;
        }
    }
    

      注册LoggerInterceptor拦截器。

    @Configuration
    public class LoggerInterceptorConfigurer implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoggerInterceptor()).addPathPatterns("/**");
        }
    }
    

    四、测试

      测试控制器类如下所示:

    @RestController
    @RequestMapping(value = "/logaop")
    public class LogAopController {
        @LogAop
        @GetMapping(value = "/test")
        public String logAopTest() {
            int a = 1/0;  //异常测试
            return "hello wolrd!";
        }
    
        @LogAop(logFlag = false)
        @GetMapping(value = "/test2")
        public String logAopTest2() {
            return "hello wolrd!";
        }
    }
    
    

      自定义拦截器请求测试:

    图4-1 自定义拦截器请求测试.png

      请求测试(携带正确的Token信息),发生异常时返回自定义提示信息:

    图4-2 请求异常返回自定义提示信息.png

      请求发生异常时,查看本地日志文件:

    图4-3 本地日志文件.png

      查看数据库日志记录信息,如下所示:

    图4-4 查看数据库日志记录信息.png

    相关文章

      网友评论

          本文标题:AOP记录日志

          本文链接:https://www.haomeiwen.com/subject/kbpjihtx.html