总概
A、技术栈
- 开发语言:Java 1.8
 - 数据库:MySQL、Redis、MongoDB、Elasticsearch
 - 微服务框架:Spring Cloud Alibaba
 - 微服务网关:Spring Cloud Gateway
 - 服务注册和配置中心:Nacos
 - 分布式事务:Seata
 - 链路追踪框架:Sleuth
 - 服务降级与熔断:Sentinel
 - ORM框架:MyBatis-Plus
 - 分布式任务调度平台:XXL-JOB
 - 消息中间件:RocketMQ
 - 分布式锁:Redisson
 - 权限:OAuth2
 - DevOps:Jenkins、Docker、K8S
 
B、源码地址
C、本节实现目标
- AOP实现API请求路径打印、请求参数打印 、执行结果打印
 
D、系列
- 微服务开发系列 第一篇:项目搭建
 - 微服务开发系列 第二篇:Nacos
 - 微服务开发系列 第三篇:OpenFeign
 - 微服务开发系列 第四篇:分页查询
 - 微服务开发系列 第五篇:Redis
 - 微服务开发系列 第六篇:Redisson
 - 微服务开发系列 第七篇:RocketMQ
 - 微服务开发系列 第八篇:Elasticsearch
 - 微服务开发系列 第九篇:OAuth2
 - 微服务开发系列 第十篇:Gateway
 - 微服务开发系列 第十一篇:XXL-JOB
 - 微服务开发系列 第十二篇:MongoDB
 - 微服务开发系列 第n篇:AOP请求日志监控
 - 微服务开发系列 第n篇:自定义校验注解
 
一、代码实现
ApiLog
package com.ac.common.apilog;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "api请求日志记录")
public class ApiLog {
    @ApiModelProperty(value = "接口耗时")
    private Long timeCost;
    @ApiModelProperty(value = "用户ID")
    private String memberId;
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "APP版本")
    private String appVersion;
    @ApiModelProperty(value = "url")
    private String url;
    @ApiModelProperty(value = "http方法 GET POST PUT DELETE PATCH")
    private String httpMethod;
    @ApiModelProperty(value = "类方法")
    private String classMethod;
    @ApiModelProperty(value = "请求参数")
    private Object requestParams;
    @ApiModelProperty(value = "返回参数")
    private Object result;
    @ApiModelProperty(value = "线程ID")
    private String threadId;
    @ApiModelProperty(value = "线程名称")
    private String threadName;
    @ApiModelProperty(value = "ip")
    private String ip;
    @ApiModelProperty(value = "是否为移动平台")
    private boolean isMobile;
    @ApiModelProperty(value = "浏览器类型")
    private String browser;
    @ApiModelProperty(value = "平台类型")
    private String platform;
    @ApiModelProperty(value = "系统类型")
    private String os;
    @ApiModelProperty(value = "引擎类型")
    private String engine;
    @ApiModelProperty(value = "浏览器版本")
    private String browserVersion;
    @ApiModelProperty(value = "引擎版本")
    private String engineVersion;
}
ApiLogAspect
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Map;
@Slf4j
@Aspect
@Component
public class ApiLogAspect {
    private static final String UNKNOWN = "unknown";
    /**
     * 日志切入点 - 正常执行<br>
     * 表达式1:拦截所有controller
     * 表达式2:排除拦截RedisTestController中的方法
     */
    @Pointcut(
            "execution(public * com.ac.*.controller.*Controller.*(..))" +
                    "&& !execution(public * com.ac.*.controller.RedisTestController.*(..))"
    )
    public void logPointCut() {
    }
    /**
     * 日志切入点 - 异常
     */
    @Pointcut(
            "execution(public * com.ac.*.controller.*Controller.*(..))"
    )
    public void logExceptionPointCut() {
    }
    /**
     * 正常执行
     *
     * @param point 切入点
     * @throws Throwable 异常信息
     */
    @Around("logPointCut()")
    public Object logAround(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = point.proceed();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            long costTime = System.currentTimeMillis() - startTime;
            String ua = request.getHeader("User-Agent");
            String appVersion = request.getHeader("App-Version");
            String memberId = request.getHeader("memberId");
            UserAgent userAgent = UserAgentUtil.parse(ua);
            ApiLog apiLog = new ApiLog();
            apiLog.setCreateTime(LocalDateTime.now());
            apiLog.setTimeCost(costTime);
            apiLog.setUrl(request.getRequestURL().toString());
            apiLog.setHttpMethod(request.getMethod());
            apiLog.setClassMethod(point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());
            apiLog.setRequestParams(extractParams(point));
            apiLog.setAppVersion(appVersion);
            apiLog.setMemberId(memberId);
            apiLog.setThreadId(Long.toString(Thread.currentThread().getId()));
            apiLog.setThreadName(Thread.currentThread().getName());
            apiLog.setIp(getIp(request));
            apiLog.setMobile(userAgent.isMobile());
            apiLog.setBrowser(userAgent.getBrowser().getName());
            apiLog.setPlatform(userAgent.getPlatform().getName());
            apiLog.setOs(userAgent.getOs().getName());
            apiLog.setEngine(userAgent.getEngine().getName());
            apiLog.setBrowserVersion(userAgent.getVersion());
            apiLog.setEngineVersion(userAgent.getEngineVersion());
            log.info("API请求时长:{}毫秒,请求信息:{}", costTime, JSONUtil.toJsonStr(apiLog));
            log.info("API请求结果,API:{},结果:{}", apiLog.getUrl(), JSONUtil.toJsonStr(result));
            if (costTime >= 100) {
                log.info("需要优化的API,{}毫秒,请求信息:{}", costTime, JSONUtil.toJsonStr(apiLog));
            }
        }
        return result;
    }
    /**
     * 异常
     *
     * @param point 切入点
     * @param e     异常
     */
    @AfterThrowing(pointcut = "logExceptionPointCut()", throwing = "e")
    public void logExceptionAround(JoinPoint point, Throwable e) {
        String classMethod = point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String url = "";
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            url = request.getRequestURL().toString();
        }
        log.error("API执行出异常了,classMethod={},url={},errorMsg={}", classMethod, url, e.getMessage());
    }
    /**
     * 获取ip
     *
     * @param request
     * @return
     */
    private String getIp(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();
        }
        String comma = ",";
        String localhost = "127.0.0.1";
        if (ip.contains(comma)) {
            ip = ip.split(",")[0];
        }
        if (localhost.equals(ip)) {
            // 获取本机真正的ip地址
            try {
                ip = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                log.error(e.getMessage(), e);
            }
        }
        return ip;
    }
    /**
     * 提取请求参数
     *
     * @param joinPoint
     * @return
     */
    private Map<String, Object> extractParams(ProceedingJoinPoint joinPoint) {
        final Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        final String[] names = methodSignature.getParameterNames();
        final Object[] args = joinPoint.getArgs();
        if (ArrayUtil.isEmpty(names) || ArrayUtil.isEmpty(args)) {
            return Collections.emptyMap();
        }
        if (names.length != args.length) {
            log.warn("{}方法参数名和参数值数量不一致", methodSignature.getName());
            return Collections.emptyMap();
        }
        Map<String, Object> map = Maps.newHashMap();
        for (int i = 0; i < names.length; i++) {
            map.put(names[i], args[i]);
        }
        return map;
    }
}

项目结构截图
二、测试
2.1 查询测试

查询测试
控制台打印日志
2023-04-27 13:47:02.298  INFO 75896 --- [nio-7010-exec-5] com.ac.common.apilog.ApiLogAspect        : API请求时长:147毫秒,请求信息:{"classMethod":"com.ac.member.controller.MemberController.findMember","os":"Unknown","ip":"172.16.11.141","requestParams":{"id":264260572479489},"httpMethod":"GET","url":"http://127.0.0.1:7010/member/264260572479489","threadName":"http-nio-7010-exec-5","platform":"Unknown","threadId":"124","createTime":1682574422285,"engine":"Unknown","browser":"Unknown","isMobile":false,"timeCost":147}
2023-04-27 13:47:02.299  INFO 75896 --- [nio-7010-exec-5] com.ac.common.apilog.ApiLogAspect        : API请求结果,API:http://127.0.0.1:7010/member/264260572479489,结果:{"birthday":643129200000,"sex":"MEN","mobile":"134178101xx","memberName":"AlanChen","id":264260572479489}
2023-04-27 13:47:02.299  INFO 75896 --- [nio-7010-exec-5] com.ac.common.apilog.ApiLogAspect        : 需要优化的API,147毫秒,请求信息:{"classMethod":"com.ac.member.controller.MemberController.findMember","os":"Unknown","ip":"172.16.11.141","requestParams":{"id":264260572479489},"httpMethod":"GET","url":"http://127.0.0.1:7010/member/264260572479489","threadName":"http-nio-7010-exec-5","platform":"Unknown","threadId":"124","createTime":1682574422285,"engine":"Unknown","browser":"Unknown","isMobile":false,"timeCost":147}
2.2 POST测试

POST测试
2023-04-27 13:48:25.307  INFO 75896 --- [nio-7010-exec-7] com.ac.common.apilog.ApiLogAspect        : API请求时长:324毫秒,请求信息:{"classMethod":"com.ac.member.controller.MemberController.recordIntegral","os":"Unknown","ip":"172.16.11.141","requestParams":{"logEditVO":{"sourceRemark":"2023-02-24下单获得积分","sourceType":"AWARD_ORDER","integral":1,"memberId":264260572479489}},"httpMethod":"POST","url":"http://127.0.0.1:7010/member/integral","threadName":"http-nio-7010-exec-7","platform":"Unknown","threadId":"126","createTime":1682574505306,"engine":"Unknown","browser":"Unknown","isMobile":false,"timeCost":324}
2023-04-27 13:48:25.307  INFO 75896 --- [nio-7010-exec-7] com.ac.common.apilog.ApiLogAspect        : API请求结果,API:http://127.0.0.1:7010/member/integral,结果:{}
2023-04-27 13:48:25.307  INFO 75896 --- [nio-7010-exec-7] com.ac.common.apilog.ApiLogAspect        : 需要优化的API,324毫秒,请求信息:{"classMethod":"com.ac.member.controller.MemberController.recordIntegral","os":"Unknown","ip":"172.16.11.141","requestParams":{"logEditVO":{"sourceRemark":"2023-02-24下单获得积分","sourceType":"AWARD_ORDER","integral":1,"memberId":264260572479489}},"httpMethod":"POST","url":"http://127.0.0.1:7010/member/integral","threadName":"http-nio-7010-exec-7","platform":"Unknown","threadId":"126","createTime":1682574505306,"engine":"Unknown","browser":"Unknown","isMobile":false,"timeCost":324}
