美文网首页
微服务开发系列 第n篇:AOP请求日志监控

微服务开发系列 第n篇:AOP请求日志监控

作者: AC编程 | 来源:发表于2023-04-26 13:56 被阅读0次

总概

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、源码地址

alanchenyan/ac-mall2-cloud

C、本节实现目标
  • AOP实现API请求路径打印、请求参数打印 、执行结果打印
D、系列

一、代码实现

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}

相关文章

网友评论

      本文标题:微服务开发系列 第n篇:AOP请求日志监控

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