一套高可用的spring AOP controller 层请求日志记录方法

1 摘要

关于使用Spring AOP 做日志记录是java 后台一种常用的日志记录方案,作者也参考了网上的很多方案,但是在实际的操作中还是遇到了一些问题:

  • POST 方式请求不能同时获取到 body 里边的 json 格式参数和常规的 parameter 中参数
  • 文件上传与下载不是抛错就是把整个文件都打印出来了

针对以上情况,作者对 AOP 的日志记录方式进行了整理

2 核心代码

切点类代码:
com.ljq.demo.springboot.web.acpect.LogAspect


package com.ljq.demo.springboot.web.acpect;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;

/**
 * @Description: 日志记录切点
 * @Author: junqiang.lu
 * @Date: 2018/11/1
 */
@Aspect
@Component
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    /**
     * controller 层切点
     */
    @Pointcut("execution(* com.ljq.demo.springboot.web.controller..*.*(..))")
    public void controllerPointcut() {
    }

    /**
     * controller 层出入参日志记录
     *
     * @param joinPoint 切点
     * @return
     */
    @Around(value = "controllerPointcut()")
    public Object controllerLogAround(ProceedingJoinPoint joinPoint) throws Throwable {
        /**
         * 获取 request 中包含的请求参数
         */
        String uuid = UUID.randomUUID().toString();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        /**
         * 获取切点请求参数(class,method)
         */
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String params = getRequestParams(request, joinPoint);

        /**
         * 入参日志
         */
        logger.info("[AOP-LOG-START]\n\trequestMark: {}\n\trequestIP: {}\n\tcontentType:{}\n\trequestUrl: {}\n\t" +
                "requestMethod: {}\n\trequestParams: {}\n\ttargetClassAndMethod: {}#{}", uuid, request.getRemoteAddr(),
                request.getHeader("Content-Type"),request.getRequestURL(), request.getMethod(), params,
                method.getDeclaringClass().getName(), method.getName());
        /**
         * 出参日志
         */
        Object result = joinPoint.proceed();
        logger.info("[AOP-LOG-END]\n\t{}", result);
        return result;
    }

    /**
     * 获取请求参数
     *
     * @param request
     * @param joinPoint
     * @return
     */
    private String getRequestParams(HttpServletRequest request, ProceedingJoinPoint joinPoint) throws JsonProcessingException {
        StringBuilder params = new StringBuilder();
        if ("GET".equalsIgnoreCase(request.getMethod())) {
            params.append(request.getQueryString());
        }
        if ("POST".equalsIgnoreCase(request.getMethod())) {
            /**
             * 获取 request parameter 中的参数
             */
            Map<String,String[]> parameterMap = request.getParameterMap();
            if (parameterMap != null && !parameterMap.isEmpty()) {
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    params.append(entry.getKey() + " = " + entry.getValue()[0] + ";");
                }
            }
            /**
             * 获取非 request parameter 中的参数
             */
            Object[] objects = joinPoint.getArgs();
            for (Object arg : objects) {
                if (arg == null) {
                    break;
                }
                String className = arg.getClass().getName().toLowerCase();
                String contentType = request.getHeader("Content-Type");
                /**
                 * 文件参数,上传文件信息
                 */
                if (className.contains("MultipartFile".toLowerCase())) {
                    MultipartFile multipartFile = (MultipartFile) arg;
                    params.append("fileSize = " + multipartFile.getSize() + ";");
                    params.append("fileContentType = " + multipartFile.getContentType() + ";");
                    params.append("fieldName = " + multipartFile.getName() + ";");
                    params.append("fileOriginalName = " + multipartFile.getOriginalFilename() + ";");
                }
                if (contentType != null && contentType.contains("application/json")){
                    /**
                     * json 参数
                     */
                    ObjectMapper mapper = new ObjectMapper();
                    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
                    mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
                    params.append(mapper.writeValueAsString(arg));
                }
            }
        }
        return params.toString();
    }


}


3 注意事项

说明:

Object[] objects = joinPoint.getArgs();

这种方式获取的参数只能获取到 POST 请求的 body中的参数,不能获取到请求头中使用 key=value 方式拼接的参数

Map<String,String[]> parameterMap = request.getParameterMap();

这种方式能够获取到 GET/POST 请求中 paramter 中的参数,即在请求头中使用 key=value方式拼接的参数,不能获取到body 中的参数

实际的项目中会有这样的场景: 使用 POST 请求方式,但是值却用 key=value 的方式传递,而非都在 body

针对文件 上传/下载 的日志记录,如果不做单独处理,会将整个文件输出到日志,这样会导致日志文件异常巨大

4 测试效果

代码行不行,直接看效果

4.1 get 请求

请求参数:

GET方式请求参数与返回结果

日志记录:

GET方式请求日志

4.2 POST请求,json格式

请求参数:
POST-json格式参数请求

日志记录:

POST-json格式请求日志

4.3 post方式请求,key=value参数

请求参数:

POST-Key=Value方式请求参数与返回结果

日志记录:

POST-key=value方式请求日志

4.4 POST方式请求,@PathVariable@RequestBody 混合

请求参数:

POST混合方式请求参数与返回结果

日志记录:

POST混合方式请求日志

4.5 POST 文件上传

请求参数:

POST文件上传请求参数与返回结果

日志记录:

POST文件上传请求日志

4.6 文件下载

请求参数:

文件下载请求参数与返回文件

日志记录:

文件现在请求日志

至此,项目中常用的请求方式都已经测试过了,文章最初提到的两个问题也得到了解决。

5 Github 源码

Github源码地址: https://github.com/Flying9001/springBootDemo/blob/master/demo-web/src/main/java/com/ljq/demo/springboot/web/acpect/LogAspect.java

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.
404Code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值