# 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`