目的:通过AOP切面,统一记录接口的访问日志
1、加maven依赖
2、 增加日志类RequestLog
3、 配置AOP切面,把请求前的request、返回的response一起记录
package com.template.common.config;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.template.common.domain.model.RequestLog;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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 java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 使用AOP切面记录请求日志
*/
@Aspect
@Component
@Slf4j
public class RequestLogger {
/**
* 切入点
*/
@Pointcut("execution(public * com.template.api.controller.*.*Controller.*(..))")
public void controllerPointcut() {
// 本方法不会被执行,只是作为一个标记,与被注解的服务方法进行关联
// 切入点为controller的public方法,也就是各个请求路径
}
/**
* 环绕操作
*
* @param joinPoint 切入点
* @return 原方法返回值
* @throws Throwable 异常信息
*/
@Around("controllerPointcut()")
public Object controllerLogging(ProceedingJoinPoint joinPoint) throws Throwable {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
// 计算各个日志参数
long startTime = System.currentTimeMillis();
Thread currentThread = Thread.currentThread();
String userHost = extractUserHost(request);
String userAgentString = request.getHeader("User-Agent");
UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString);
Map<String, Object> requestParams = extractRequestParams(joinPoint);
Object responseResult = joinPoint.proceed();
Signature controllerMethod = joinPoint.getSignature();
String classMethod = controllerMethod.getDeclaringTypeName() + "." + controllerMethod.getName();
final RequestLog requestLog = RequestLog
.builder()
.userHost(userHost)
.userOs(userAgent.getOperatingSystem().getName())
.userBrowser(userAgent.getBrowser().getName())
.userAgent(userAgentString)
.requestUrl(request.getRequestURL().toString())
.requestMethod(request.getMethod())
.requestParams(requestParams)
.responseResult(responseResult)
.classMethod(classMethod)
.threadId(Long.toString(currentThread.getId()))
.threadName(currentThread.getName())
.costMillisecond(System.currentTimeMillis() - startTime)
.build();
// 打印日志
log.info("Request Log Info : {}", JSONUtil.toJsonStr(requestLog));
return responseResult;
}
/**
* 获取用户Host地址
*
* @param request 请求对象
* @return 用户Host
*/
private static String extractUserHost(HttpServletRequest request) {
String xRealIp = request.getHeader("X-Real-IP");
if (StrUtil.isNotEmpty(xRealIp) && !"unknown".equalsIgnoreCase(xRealIp)) {
return xRealIp;
}
String xForwardedFor = request.getHeader("x-forwarded-for");
if (StrUtil.isNotEmpty(xForwardedFor) && !"unknown".equalsIgnoreCase(xForwardedFor)) {
return xForwardedFor;
}
String proxyClientIp = request.getHeader("Proxy-Client-IP");
if (StrUtil.isNotEmpty(proxyClientIp) && !"unknown".equalsIgnoreCase(proxyClientIp)) {
return proxyClientIp;
}
String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
if (StrUtil.isNotEmpty(wlProxyClientIp) && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
return wlProxyClientIp;
}
String httpClientIp = request.getHeader("HTTP_CLIENT_IP");
if (StrUtil.isNotEmpty(httpClientIp) && !"unknown".equalsIgnoreCase(httpClientIp)) {
return httpClientIp;
}
String httpxForwardedFor = request.getHeader("HTTP_X_FORWARDED_FOR");
if (StrUtil.isNotEmpty(httpxForwardedFor) && !"unknown".equalsIgnoreCase(httpxForwardedFor)) {
return httpxForwardedFor;
}
String remoteAddr = request.getRemoteAddr();
if (!"127.0.0.1".equals(remoteAddr) && !"0:0:0:0:0:0:0:1".equals(remoteAddr)) {
return remoteAddr;
}
//根据网卡取本机IP地址
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("getIpAddress exception:", e);
}
return xRealIp;
}
/**
* 获取请求参数
*
* @param joinPoint 切入点
* @return 请求参数
*/
private Map<String, Object> extractRequestParams(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
final String[] parameterNames = methodSignature.getParameterNames();
final Object[] parameterValues = joinPoint.getArgs();
if (ArrayUtil.isEmpty(parameterNames) || ArrayUtil.isEmpty(parameterValues)) {
return Collections.emptyMap();
}
if (parameterNames.length != parameterValues.length) {
log.warn("{}方法参数名和参数值数量不一致", methodSignature.getName());
return Collections.emptyMap();
}
Map<String, Object> requestParams = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
requestParams.put(parameterNames[i], parameterValues[i]);
}
return requestParams;
}
}
4、查看效果
也可以把日志写入到数据库里面,自由发挥。