puzhibing
2026-04-10 ed838e62602c0a63a64ff4bd144a0d16cc9a7cf8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.xiaopeng.automaticPilot.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 调用日志切面
 *
 * <p>拦截所有带 @FeignClient 注解的接口方法,记录:
 * <ul>
 *   <li>调用的接口类名 + 方法名</li>
 *   <li>入参(JSON 序列化)</li>
 *   <li>出参(JSON 序列化)</li>
 *   <li>耗时(ms)</li>
 *   <li>异常信息(含完整堆栈)</li>
 * </ul>
 */
@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<String> 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<String> 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() + "}";
        }
    }
}