总概
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
- 微服务开发系列 第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}
网友评论