package com.xiaopeng.operation.config.log;
import com.alibaba.fastjson2.JSON;
import com.xiaopeng.common.log.config.LogHeaderProperties;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
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.lang.reflect.Method;
import java.util.*;
/**
* Feign 调用日志切面
*
*
拦截所有带 @FeignClient 注解的接口方法,记录:
*
* - 调用的接口类名 + 方法名
* - 入参(JSON 序列化)
* - 出参(JSON 序列化)
* - 耗时(ms)
* - 异常信息(含完整堆栈)
*
*/
@Slf4j
@Aspect
@Component
public class FeignLoggingAspect {
@Autowired
private LogHeaderProperties logHeaderProperties;
/**
* 拦截所有带 @FeignClient 注解的接口的所有方法
*/
@Around("within(@org.springframework.cloud.openfeign.FeignClient *)")
public Object logFeignCall(ProceedingJoinPoint pjp) throws Throwable {
return doLog(pjp);
}
private Object doLog(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
// 使用接口声明类名,避免打印动态代理类名(如 $Proxy270)
String className = signature.getDeclaringType().getSimpleName();
String methodName = method.getName();
String fullMethod = className + "#" + methodName;
Object[] args = pjp.getArgs();
String argsJson = safeToJson(args);
String headerStr = extractCurrentHeaders();
if (headerStr != null) {
log.info("[Feign] >>> 调用开始 | method={} | headers={} | args={}", fullMethod, headerStr, argsJson);
} else {
log.info("[Feign] >>> 调用开始 | method={} | args={}", fullMethod, argsJson);
}
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
long elapsed = System.currentTimeMillis() - start;
String resultJson = safeToJson(result);
log.info("[Feign] <<< 调用成功 | method={} | elapsed={}ms | result={}", fullMethod, elapsed, resultJson);
return result;
} catch (Throwable ex) {
long elapsed = System.currentTimeMillis() - start;
log.error("[Feign] <<< 调用异常 | method={} | elapsed={}ms | error={} | args={}",
fullMethod, elapsed, ex.getMessage(), argsJson, ex);
throw ex;
}
}
/**
* 安全序列化为 JSON,避免序列化失败导致日志切面本身抛异常
*/
private String safeToJson(Object obj) {
if (obj == null) {
return "null";
}
try {
if (obj instanceof Object[]) {
return JSON.toJSONString(Arrays.asList((Object[]) obj));
}
return JSON.toJSONString(obj);
} catch (Exception e) {
return "[序列化失败: " + obj.getClass().getSimpleName() + "]";
}
}
/**
* 从当前请求上下文中提取请求头信息
*/
private String extractCurrentHeaders() {
if (!logHeaderProperties.isEnabled()) {
return null;
}
try {
ServletRequestAttributes attrs = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (attrs == null) return "{}";
HttpServletRequest request = attrs.getRequest();
List names = logHeaderProperties.getNames();
StringBuilder sb = new StringBuilder("{");
boolean first = true;
if (names != null && !names.isEmpty()) {
for (String name : names) {
String value = request.getHeader(name);
if (value != null) {
if (!first) sb.append(", ");
first = false;
sb.append(name).append("=").append(value);
}
}
} else {
Enumeration headerNames = request.getHeaderNames();
while (headerNames != null && headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
if (!first) sb.append(", ");
first = false;
sb.append(name).append("=").append(value);
}
}
return sb.append("}").toString();
} catch (Exception e) {
return "{error: " + e.getMessage() + "}";
}
}
}