# feign-log-spring-boot-starter
**Repository Path**: BeALaity/feign-log-spring-boot-starter
## Basic Information
- **Project Name**: feign-log-spring-boot-starter
- **Description**: No description available
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-08-08
- **Last Updated**: 2026-08-08
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# feign-log-spring-boot-starter
Feign调用日志记录Starter,通过自定义注解控制是否记录Feign调用的请求和响应日志,支持与 `feign-unwrap-spring-boot-starter` 集成。
## 功能特性
- **注解驱动**:`@FeignLog` 标注在Feign接口类或方法上,方法级优先于类级
- **请求日志**:记录HTTP方法、URL、Header(可选)、Body(可截断)
- **响应日志**:记录状态码、Header(可选)、Body(可截断)、耗时
- **解包集成**:若 `feign-unwrap-spring-boot-starter` 在类路径上,自动识别 `FeignUnwrapException` 并记录"解包失败"
- **HTTP状态判断**:无解包注解时,默认根据HTTP响应码200判断成功/失败
- **数据实体**:提供 `FeignLogEntity` 承载完整调用数据(请求时间、耗时、请求/响应头体、链路追踪ID等),通过 `FeignLogCallback` 回调暴露供入库
- **装饰器模式**:包装原始Client和InvocationHandlerFactory,不侵入用户现有配置
- **双重注册**:同时支持Spring Boot 2.x(spring.factories)和3.x(AutoConfiguration.imports)
## 安装
```xml
com.xiaokong
feign-log-spring-boot-starter
1.0.0-SNAPSHOT
```
与解包Starter配合使用(可选):
```xml
com.xiaokong
feign-unwrap-spring-boot-starter
1.0.0-SNAPSHOT
```
## 使用示例
### 基本用法
```java
@FeignClient(name = "user-service")
public interface UserFeignClient {
// 方法级注解
@FeignLog
User getUser(@PathVariable Long id);
// 不加注解的方法不会记录日志
void deleteUser(@PathVariable Long id);
}
```
### 类级注解(所有方法生效)
```java
@FeignClient(name = "user-service")
@FeignLog
public interface UserFeignClient {
User getUser(@PathVariable Long id);
List listUsers();
}
```
### 自定义参数
```java
@FeignLog(logHeaders = true, maxBodyLength = 4096)
User createUser(@RequestBody User user);
```
### 与解包Starter配合
```java
@FeignClient(name = "user-service")
@FeignUnwrap(Result.class) // 解包注解
@FeignLog // 日志注解
public interface UserFeignClient {
// 解包成功 → 记录"调用成功"
// 解包失败 → 记录"解包失败"(FeignUnwrapException被捕获)
// 无解包注解的方法 → 根据HTTP 200判断成功/失败
User getUser(@PathVariable Long id);
}
```
### 禁用单个方法日志
```java
@FeignLog
public interface UserFeignClient {
@FeignLog(enabled = false) // 覆盖类级配置,禁用此方法日志
void healthCheck();
}
```
## 注解参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `enabled` | boolean | true | 是否启用日志记录 |
| `toSysName` | String | "" | 调用方系统名(覆盖@FeignClient的name) |
| `requestType` | String | "feign" | 请求类型标识(feign/client/http等) |
| `logHeaders` | boolean | false | 是否记录Header(可能含敏感信息) |
## 数据实体与入库
### FeignLogEntity
每次Feign调用完成后,Starter自动填充一个 `FeignLogEntity`,包含以下字段:
| 分类 | 字段 | 类型 | 说明 |
|---|---|---|---|
| **基础信息** | `traceId` | String | 链路追踪ID(优先X-Trace-Id请求头,其次MDC) |
| | `fromSys` | String | 请求方系统名(默认spring.application.name) |
| | `toSys` | String | 调用方系统名(默认@FeignClient的name) |
| | `requestType` | String | 请求类型(feign/client/http等) |
| | `className` | String | 接口全限定名 |
| | `methodName` | String | 方法名 |
| **请求信息** | `requestUrl` | String | 完整请求URL |
| | `requestMethod` | String | GET/POST/PUT/DELETE |
| | `requestHeaders` | Map | 请求头 |
| | `requestBody` | String | 请求体 |
| | `requestTime` | Date | 请求时间(yyyy-MM-dd HH:mm:ss.SSS) |
| **响应信息** | `responseStatus` | Integer | HTTP响应状态码 |
| | `responseHeaders` | Map | 响应头 |
| | `responseBody` | String | 响应体 |
| | `responseTime` | Date | 响应时间(yyyy-MM-dd HH:mm:ss.SSS) |
| **执行结果** | `elapsed` | Long | 耗时(毫秒) |
| | `success` | Boolean | 是否成功 |
| | `errorType` | ErrorType | NONE/UNWRAP/HTTP/EXCEPTION |
| | `errorMessage` | String | 错误信息 |
### FeignLogCallback 回调接口
实现 `FeignLogCallback` 并注册为 Spring Bean,每次调用完成后自动回调:
```java
@Component
public class MyFeignLogCallback implements FeignLogCallback {
@Override
public void onComplete(FeignLogEntity entity) {
// 入库
feignLogMapper.insert(entity);
}
}
```
> 多个 `FeignLogCallback` 会被全部调用。单个回调异常不影响其他回调执行。
## 配置属性
```yaml
feign-log:
enabled: true # 全局开关,false则禁用所有日志
from-sys: my-service # 请求方系统名(不配则取spring.application.name)
log-headers: false # 全局Header记录开关
max-body-length: 2048 # 全局Body最大长度
```
> 注解参数优先于全局配置。未标注 `@FeignLog` 的方法不记录日志。
## 日志输出示例
### 调用成功
```
[FeignLog] >>> 请求: GET http://user-service/api/user/1, body=(empty)
[FeignLog] <<< 响应: GET http://user-service/api/user/1, status=200, 耗时=56ms, body={"code":200,"data":{"id":1,"name":"张三"}}
[FeignLog] 调用成功: UserFeignClient#getUser, 耗时=58ms
```
### 解包失败
```
[FeignLog] >>> 请求: GET http://user-service/api/user/999, body=(empty)
[FeignLog] <<< 响应: GET http://user-service/api/user/999, status=200, 耗时=55ms, body={"code":500,"msg":"用户不存在"}
[FeignLog] 解包失败: UserFeignClient#getUser, 耗时=57ms, 异常=Feign解包失败: code=500, msg=用户不存在
```
### HTTP调用失败
```
[FeignLog] >>> 请求: POST http://user-service/api/user, body={"name":"test"}
[FeignLog] <<< 响应: POST http://user-service/api/user, status=500, 耗时=120ms, body={"error":"Internal Server Error"}
[FeignLog] HTTP调用失败: UserFeignClient#createUser, status=500, 耗时=122ms, 异常=Internal Server Error
```
### HTTP状态码非200
```
[FeignLog] >>> 请求: POST http://user-service/api/user, body={"name":"test"}
[FeignLog] <<< 响应: POST http://user-service/api/user, status=201, 耗时=45ms, body={"id":100}
[FeignLog] HTTP状态码非200: UserFeignClient#createUser, status=201, 耗时=47ms
```
## 架构设计
### 组件职责
| 组件 | 职责 |
|---|---|
| `@FeignLog` | 注解,控制是否记录日志及日志参数 |
| `FeignLogEntity` | 实体,承载完整调用数据(请求/响应/耗时/错误) |
| `FeignLogCallback` | 回调接口,用户实现后将实体入库 |
| `FeignLogContext` | ThreadLocal上下文,在Handler和Client间传递Method、ResponseStatus和Entity |
| `FeignLogClient` | Client装饰器,记录HTTP请求/响应日志,缓冲响应体,填充实体请求/响应信息 |
| `FeignLogInvocationHandlerFactory` | Handler装饰器,创建实体、设置ThreadLocal、捕获异常判断成功/失败、回调 |
| `FeignLogAutoConfiguration` | 自动配置,收集Callback Bean,通过FeignBuilderCustomizer包装Client和Handler |
### 实体填充时序
```
Handler创建实体 → 填充fromSys/toSys/requestType/className/methodName/requestTime
│
▼
Client执行前 → 填充traceId/requestUrl/Method/Headers/Body
│
▼
Client执行后 → 填充responseStatus/Headers/Body/elapsed/responseTime
│
▼
Handler回调 → 填充success/errorType/errorMessage → 回调用户Callback
```
### 与解包Starter的协作
两个Starter都包装 `InvocationHandlerFactory`,装饰器模式支持嵌套:
```
调用链(两种顺序均可):
顺序A: LogHandler(UnwrapHandler(Default))
→ Log设置ThreadLocal → Unwrap设置ThreadLocal → Default执行
→ Decoder抛FeignUnwrapException → 冒泡到Log的catch → 记录"解包失败" + 实体设置errorType=UNWRAP
顺序B: UnwrapHandler(LogHandler(Default))
→ Unwrap设置ThreadLocal → Log设置ThreadLocal → Default执行
→ Decoder抛FeignUnwrapException → 冒泡经过Unwrap(finally清理) → Log的catch → 记录"解包失败" + 实体设置errorType=UNWRAP
```
两个Starter各自维护独立的ThreadLocal,互不干扰。
### 成功/失败判断逻辑
```
方法调用
│
├─ 抛出FeignUnwrapException → 记录"解包失败" + entity.errorType=UNWRAP
│ (feign-unwrap-spring-boot-starter在类路径上时识别)
│
├─ 抛出FeignException → 记录"HTTP调用失败" + entity.errorType=HTTP + 状态码
│ (Feign默认对4xx/5xx抛出FeignException)
│
├─ 抛出其他异常 → 记录"调用异常" + entity.errorType=EXCEPTION
│
└─ 无异常 → 检查HTTP响应码
├─ status == 200 → 记录"调用成功" + entity.success=true
└─ status != 200 → 记录"HTTP状态码非200" + entity.errorType=HTTP
```
## 遵循的开发规范
- **包结构**:`com.xiaokong.feignlog` 下分 `annotation`、`context`、`properties`、`client`、`handler`、`model`
- **pom.xml**:无parent POM,Java 8,版本管理在 ``,`optional` 依赖隔离
- **双重注册**:`spring.factories`(2.x)+ `AutoConfiguration.imports`(3.x)
- **Bean装配**:`@ConditionalOnClass` + `@ConditionalOnProperty` + `@Order(LOWEST_PRECEDENCE)`
- **Javadoc**:`@author Hu_bx` + `@date` + 中文注释
- **Hutool**:`ReflectUtil`、`ObjUtil`、`StrUtil`、`IoUtil`、`AnnotationUtil`