MENU

SpringBoot 注解实现日志切面

March 21, 2022 • 技术阅读设置

通过自定义注解的方式,在 Spring Boot 中实现 AOP 切面统一打印出入参日志。

步骤

1.定义自定义注解。
2.编写AOP切面方法。
3.在需要输出日志的方法上使用注解。

代码

maven依赖

<!-- aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- hutool -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.9</version>
</dependency>
<!-- commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

自定义注解

package com.example.practise.annotation;

import java.lang.annotation.*;

/**
 * description: 注解 打印请求及应答信息
 * date: 2022/03/21 15:38
 * author: LenonJin
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface WebLog {

    /**
     * 方法描述
     */
    String description() default "";

    /**
     * 是否打印request
     */
    boolean printReq() default true;

    /**
     * 是否打印response
     */
    boolean printRes() default true;

    /**
     * 是否脱敏request
     */
    boolean sensitiveReq() default false;

    /**
     * 是否脱敏response
     */
    boolean sensitiveRes() default false;
}

AOP切面

package com.example.practise.aspectj;

import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.example.practise.annotation.WebLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
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.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * description: 切面 打印请求及应答信息
 * date: 2022/03/21 14:37
 * author: LenonJin
 */
@Slf4j
@Aspect
@Component
public class WebLogAspect {

    /**
     * 切点
     */
    @Pointcut("@annotation(com.example.practise.annotation.WebLog)")
    public void pointCut() {
    }

    /**
     * description: 环绕切面
     * date: 2022/03/21 14:45
     * author: LenonJin
     *
     * @param proceedingJoinPoint
     * @param logPrint
     * @return java.lang.Object
     */
    @Around("pointCut() && @annotation(logPrint)")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint, WebLog logPrint) throws Throwable {
        log.info("----------------------------------------------------------- Start -----------------------------------------------------------");
        // 耗时情况
        long startTime = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 请求参数是否打印及脱敏
        String requestStr = "";
        requestStr = logPrint.printReq() ? getRequestArgs(request, proceedingJoinPoint) : "请求参数打印关闭.";
        requestStr = logPrint.sensitiveReq() ? sensitive(requestStr) : requestStr;
        // 打印请求信息
        log.info("Request Uri    :{}", request.getRequestURI());
        log.info("Request Ip     :{}", StringUtils.isBlank(request.getHeader("X-Real-IP")) ? request.getRemoteAddr() : request.getHeader("X-Real-IP"));
        log.info("Http Method    :{}", request.getMethod());
        log.info("Description    :{}", logPrint.description());
        log.info("Class Method   :{}.{}", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
        log.info("Request Args   :{}", requestStr);

        Object result = "";
        result = proceedingJoinPoint.proceed();

        // 应答参数是否打印及脱敏
        String responseStr = "";
        responseStr = logPrint.printRes() ? JSONUtil.toJsonStr(result) : "应答参数打印关闭.";
        responseStr = logPrint.sensitiveRes() ? sensitive(responseStr) : responseStr;
        // 打印应答信息
        log.info("Response Args  :{}", responseStr);
        log.info("Time Consuming :{} ms", System.currentTimeMillis() - startTime);
        log.info("------------------------------------------------------------ End ------------------------------------------------------------");

        return result;
    }

    /**
     * description: 判断请求类型
     * date: 2022/03/21 14:45
     * author: LenonJin
     *
     * @param request
     * @return boolean
     */
    private boolean isJsonRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
        }
        return false;
    }

    /**
     * description: 获取请求参数
     * date: 2022/11/17 14:45
     * author: LenonJin
     *
     * @param request
     * @param joinPoint
     * @return java.lang.String
     */
    String getRequestArgs(HttpServletRequest request, JoinPoint joinPoint) {
        // 获取请求参数
        if (isJsonRequest(request)) {
            // ServletRequest、ServletResponse、MultipartFile 不能被序列化
            Object[] args = joinPoint.getArgs();
            if (ArrayUtils.isNotEmpty(args)) {
                List<Object> logArgs = Arrays.stream(args).filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
                        .collect(Collectors.toList());
                return JSONUtil.toJsonStr(logArgs);
            } else {
                return "args is null.";
            }
        } else {
            Map<String, String[]> parameterMap = request.getParameterMap();
            if (MapUtil.isNotEmpty(parameterMap)) {
                String parameters = JSONUtil.toJsonStr(parameterMap);
                return parameters;
            } else {
                String parameters = StringUtils.isBlank(request.getQueryString()) ? "args is null." : request.getQueryString();
                return parameters;
            }
        }
    }

    /**
     * description: 字符串脱敏处理
     * date: 2022/03/21 14:45
     * author: LenonJin
     *
     * @param str
     * @return java.lang.String
     */
    String sensitive(String str) {
        if (StringUtils.isNotBlank(str)) {
            // 手机号脱敏
            str = str.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
            // TODO 按需拓展
        }
        log.info("Tips           :{}", "敏感数据已脱敏.");
        return str;
    }

}

注解使用

测试demo

package com.example.practise.controller;

import com.example.practise.annotation.WebLog;
import org.springframework.web.bind.annotation.*;

/**
 * description: 测试日志打印
 * date: 2022/03/21 15:42
 * author: LenonJin
 */
@RestController
@RequestMapping("/log")
public class LogController {

    /**
     * test @RequestParam
     */
    @WebLog(description = "测试接口一")
    @GetMapping("/getBookNameById")
    public String getBookNameById(@RequestParam("id") String id) {
        return "平凡的世界";
    }

    /**
     * test @PathVariable
     */
    @WebLog(description = "测试接口二")
    @GetMapping("/getBookById/{id}")
    public BookRes getBookByTitle(@PathVariable("id") String id) {
        BookRes bookRes = new BookRes();
        bookRes.setAuthor("LenonJin");
        bookRes.setName("JAVA从入门到放弃");
        bookRes.setDesc("隐形贫困人口:指某些人看起来有吃有喝有玩,但实际上非常穷。");
        return bookRes;
    }

    /**
     * test post
     */
    @WebLog(description = "测试接口三")
    @PostMapping("/getBook")
    public BookRes getBook(@RequestBody BookReq request) {
        BookRes bookRes = new BookRes();
        bookRes.setAuthor("毛姆");
        bookRes.setName("月亮与六便士");
        bookRes.setDesc("理想(月亮)和现实(六便士)如何选择?这是摆在每个人面前的选择。");
        return bookRes;
    }

    /**
     * test printReq use
     */
    @WebLog(description = "测试关闭请求参数打印", printReq = false)
    @GetMapping("/testOne")
    public String testOne(@RequestParam("password") String password) {
        return "SUCCESS";
    }

    /**
     * test sensitiveReq use
     */
    @WebLog(description = "测试请求参数脱敏", sensitiveReq = true)
    @GetMapping("/testTwo")
    public String testTwo(@RequestParam("mobile") String mobile) {
        return "SUCCESS";
    }
}

打印效果

2022-03-21 16:16:59.905  INFO 3776 --- [nio-8080-exec-5] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-03-21 16:16:59.905  INFO 3776 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-03-21 16:16:59.911  INFO 3776 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : Completed initialization in 6 ms
2022-03-21 16:16:59.914  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:16:59.915  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Request Uri    :/log/getBookNameById
2022-03-21 16:16:59.915  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Request Ip     :127.0.0.1
2022-03-21 16:16:59.916  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Http Method    :GET
2022-03-21 16:16:59.916  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Description    :测试接口一
2022-03-21 16:16:59.916  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Class Method   :com.example.practise.controller.LogController.getBookNameById
2022-03-21 16:16:59.916  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Request Args   :{"id":["200"]}
2022-03-21 16:16:59.920  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Response Args  :平凡的世界
2022-03-21 16:16:59.920  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : Time Consuming :6 ms
2022-03-21 16:16:59.920  INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect  : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:01.569  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Request Uri    :/log/getBookById/200
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Request Ip     :127.0.0.1
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Http Method    :GET
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Description    :测试接口二
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Class Method   :com.example.practise.controller.LogController.getBookByTitle
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Request Args   :args is null.
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Response Args  :{"name":"JAVA从入门到放弃","desc":"隐形贫困人口:指某些人看起来有吃有喝有玩,但实际上非常穷。","author":"LenonJin"}
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : Time Consuming :1 ms
2022-03-21 16:17:01.570  INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect  : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:02.971  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:02.976  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Request Uri    :/log/getBook
2022-03-21 16:17:02.976  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Request Ip     :127.0.0.1
2022-03-21 16:17:02.976  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Http Method    :POST
2022-03-21 16:17:02.977  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Description    :测试接口三
2022-03-21 16:17:02.977  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Class Method   :com.example.practise.controller.LogController.getBook
2022-03-21 16:17:02.977  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Request Args   :[{"name":"月亮与六便士","desc":""}]
2022-03-21 16:17:02.978  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Response Args  :{"name":"月亮与六便士","desc":"理想(月亮)和现实(六便士)如何选择?这是摆在每个人面前的选择。","author":"毛姆"}
2022-03-21 16:17:02.978  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : Time Consuming :6 ms
2022-03-21 16:17:02.978  INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect  : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Request Uri    :/log/testOne
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Request Ip     :127.0.0.1
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Http Method    :GET
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Description    :测试关闭请求参数打印
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Class Method   :com.example.practise.controller.LogController.testOne
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Request Args   :请求参数打印关闭.
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Response Args  :SUCCESS
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : Time Consuming :0 ms
2022-03-21 16:17:04.312  INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect  : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:05.918  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Tips           :敏感数据已脱敏.
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Request Uri    :/log/testTwo
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Request Ip     :127.0.0.1
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Http Method    :GET
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Description    :测试请求参数脱敏
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Class Method   :com.example.practise.controller.LogController.testTwo
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Request Args   :{"mobile":["176****0007"]}
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Response Args  :SUCCESS
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : Time Consuming :1 ms
2022-03-21 16:17:05.919  INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect  : ------------------------------------------------------------ End ------------------------------------------------------------

说明

自定义注解:
@Retention(RetentionPolicy.RUNTIME):定义为运行时使用该注解。
@Target({ElementType.METHOD}):定义注解作用于方法上。

切面:
@Pointcut("@annotation(com.example.practise.annotation.WebLog)"):切点,这里的切点切的就是我们的自定义注解WebLog。
@Around("pointCut() && @annotation(logPrint)"): 环绕,可以在切入点前后织入代码,可以自由的控制何时执行切点。

拓展:
切面中(WebLogAspect 143行),脱敏部分的正则表达式可以按照自己的要求添加或修改。

Last Modified: November 17, 2023