概述
Feign 默认不打印任何远程调用日志,所以"接口能调通但控制台干干净净"是正常行为。这篇把 Feign 的可配置项梳理一遍,重点落在最常改、也最容易改错的日志级别上:四档级别到底是什么、两种配置方式的完整写法、作用域优先级,以及"改了 logging.level 却依然没日志"这类高频坑。
纲要
- Feign 支持哪些自定义配置
feign.Logger.Level日志级别feign.codec.Decoder/feign.codec.Encoder编解码器feign.Contract注解契约feign.Retryer失败重试- 自定义方式:用
@Bean覆盖默认 Bean
- 日志级别四档
NONE/BASIC/HEADERS/FULL各自记录什么- 默认值是
NONE FULL打印请求体的安全风险- Feign 的
Logger.Level与日志框架logging.level的区别
- 方式一:配置文件
feign.client.config.<服务名>.loggerLevel- 用
default做全局 - 配套的
logging.level.cn.itcast: debug
- 方式二:Java 代码
@Bean public Logger.Level feignLogLevel()- 全局:
@EnableFeignClients(defaultConfiguration = ...) - 局部:
@FeignClient(configuration = ...) - 作用域坑:
@Bean放在启动类扫描范围内会全局生效
- 优先级与验证方式
- 实战坑清单
- API 速览 / 官方文档 / 总结
Feign 支持哪些自定义配置
SpringBoot 给 Feign 做了一套自动装配,同时留了覆盖默认值的口子。常用的可配置项如下,讲义 2.2 节给出的就是这张表:
| 类型 | 作用 | 说明 |
|---|---|---|
feign.Logger.Level |
修改日志级别 | 四档:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder |
响应结果解析器 | 把远程返回的 JSON 字符串解析成 Java 对象 |
feign.codec.Encoder |
请求参数编码 | 把方法参数编码成 HTTP 请求体 |
feign.Contract |
支持的注解格式 | 默认是 SpringMVC 注解,一般不动 |
feign.Retryer |
失败重试机制 | Feign 自身默认不重试,实际重试由底层 Ribbon 提供 |
自定义的统一手法就是声明一个同类型的 @Bean 覆盖默认实现 。日常开发里前四类基本用不上------Decoder/Encoder 默认的 Jackson 实现够用,Contract 默认就支持 @GetMapping 这些熟悉注解。真正会动手调的,九成场景是日志级别:方法、URL、状态码、耗时、请求头、请求体这些信息只有把日志打开才看得见,排查 401/404/序列化失败时非常关键。
下面只讲日志级别,其余配置项的写法可以照搬这个套路。
日志级别四档
feign.Logger.Level 是个枚举,四个取值记录的内容逐级递增:
| 级别 | 记录内容 | 适用场景 |
|---|---|---|
NONE |
什么都不记,这是默认值 | 生产环境长期运行 |
BASIC |
请求方法、URL、响应状态码、执行耗时 | 日常开发、联调 |
HEADERS |
在 BASIC 之上,额外记录请求头与响应头 | 排查鉴权头、Content-Type 问题 |
FULL |
在 HEADERS 之上,再记录请求体与响应体 | 本地调试,短时开启 |
两个必须先说清楚的结论:
默认是 NONE,所以"Feign 调用没有日志"不是 bug。 很多人第一次用 Feign,看到控制台只有 MyBatis 的 SQL 日志,就以为远程调用没走到或者依赖没生效。其实请求已经发出去了,只是日志开关没打开------毕竟生产环境全量打请求体,日志量和性能开销都不小。
FULL 会完整打印请求体和响应体,里面可能带密码、手机号、身份证号、token。 本地开发时开 FULL 看报文没问题,但把 loggerLevel: FULL 提交到生产分支是很危险的事:一方面敏感字段直接落到日志文件里,另一方面单次调用四段文本、高 QPS 下磁盘很快会被刷满。生产建议保持 NONE,或者最多 BASIC。
别把 Feign 的 Level 和 logging.level 混为一谈
这是最容易踩的坑,两套东西名字里都有 "level",但管的是完全不同的层:
| Feign 日志级别 | 日志框架级别 | |
|---|---|---|
| 配置项 | feign.client.config.<服务>.loggerLevel 或 @Bean Logger.Level |
logging.level.<包名> |
| 取值 | NONE / BASIC / HEADERS / FULL |
TRACE / DEBUG / INFO / WARN / ERROR |
| 作用 | 决定 Feign 要不要生成这段日志内容 | 决定日志框架要不要输出这段内容 |
日志从上到下要过两道闸门:
#mermaid-svg-c4sXUuCldUmHtR4q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c4sXUuCldUmHtR4q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c4sXUuCldUmHtR4q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c4sXUuCldUmHtR4q .error-icon{fill:#552222;}#mermaid-svg-c4sXUuCldUmHtR4q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c4sXUuCldUmHtR4q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c4sXUuCldUmHtR4q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c4sXUuCldUmHtR4q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c4sXUuCldUmHtR4q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c4sXUuCldUmHtR4q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c4sXUuCldUmHtR4q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c4sXUuCldUmHtR4q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c4sXUuCldUmHtR4q .marker.cross{stroke:#333333;}#mermaid-svg-c4sXUuCldUmHtR4q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c4sXUuCldUmHtR4q p{margin:0;}#mermaid-svg-c4sXUuCldUmHtR4q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c4sXUuCldUmHtR4q .cluster-label text{fill:#333;}#mermaid-svg-c4sXUuCldUmHtR4q .cluster-label span{color:#333;}#mermaid-svg-c4sXUuCldUmHtR4q .cluster-label span p{background-color:transparent;}#mermaid-svg-c4sXUuCldUmHtR4q .label text,#mermaid-svg-c4sXUuCldUmHtR4q span{fill:#333;color:#333;}#mermaid-svg-c4sXUuCldUmHtR4q .node rect,#mermaid-svg-c4sXUuCldUmHtR4q .node circle,#mermaid-svg-c4sXUuCldUmHtR4q .node ellipse,#mermaid-svg-c4sXUuCldUmHtR4q .node polygon,#mermaid-svg-c4sXUuCldUmHtR4q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c4sXUuCldUmHtR4q .rough-node .label text,#mermaid-svg-c4sXUuCldUmHtR4q .node .label text,#mermaid-svg-c4sXUuCldUmHtR4q .image-shape .label,#mermaid-svg-c4sXUuCldUmHtR4q .icon-shape .label{text-anchor:middle;}#mermaid-svg-c4sXUuCldUmHtR4q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c4sXUuCldUmHtR4q .rough-node .label,#mermaid-svg-c4sXUuCldUmHtR4q .node .label,#mermaid-svg-c4sXUuCldUmHtR4q .image-shape .label,#mermaid-svg-c4sXUuCldUmHtR4q .icon-shape .label{text-align:center;}#mermaid-svg-c4sXUuCldUmHtR4q .node.clickable{cursor:pointer;}#mermaid-svg-c4sXUuCldUmHtR4q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c4sXUuCldUmHtR4q .arrowheadPath{fill:#333333;}#mermaid-svg-c4sXUuCldUmHtR4q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c4sXUuCldUmHtR4q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c4sXUuCldUmHtR4q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c4sXUuCldUmHtR4q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c4sXUuCldUmHtR4q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c4sXUuCldUmHtR4q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c4sXUuCldUmHtR4q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c4sXUuCldUmHtR4q .cluster text{fill:#333;}#mermaid-svg-c4sXUuCldUmHtR4q .cluster span{color:#333;}#mermaid-svg-c4sXUuCldUmHtR4q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c4sXUuCldUmHtR4q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c4sXUuCldUmHtR4q rect.text{fill:none;stroke-width:0;}#mermaid-svg-c4sXUuCldUmHtR4q .icon-shape,#mermaid-svg-c4sXUuCldUmHtR4q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c4sXUuCldUmHtR4q .icon-shape p,#mermaid-svg-c4sXUuCldUmHtR4q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c4sXUuCldUmHtR4q .icon-shape .label rect,#mermaid-svg-c4sXUuCldUmHtR4q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c4sXUuCldUmHtR4q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c4sXUuCldUmHtR4q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c4sXUuCldUmHtR4q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
否, 如 INFO
是, 如 DEBUG
Feign 发起远程调用
Feign Level 是 NONE?
不生成日志内容
按 Level 生成日志内容
接口所在包的 logger 级别够低?
内容被框架拦掉, 看不到
控制台输出日志
两道闸门同时打开才看得到日志。Feign 生成的日志最终是交给该 Feign 接口所在包 的 logger 去输出的,所以配置文件里那句 logging.level.cn.itcast: debug 不是可选项------它决定了日志能不能落地。反过来,只改 logging.level 而不配 Feign 的 Level,一样什么都看不到,因为 Feign 压根没生成内容,框架自然没东西可打。
方式一:配置文件
先给出工程结构,本文涉及的文件都落在这几个模块里:
tree
cloud-demo
├── order-service # 订单服务,Feign 的调用方
│ ├── src/main/java/cn/itcast/order
│ │ ├── OrderApplication.java # 启动类,@EnableFeignClients 在这里
│ │ └── service/OrderService.java # 通过 UserClient 发起远程调用
│ └── src/main/resources/application.yml # 方式一:在这里配 loggerLevel
├── feign-api # 抽取出来的 Feign 客户端模块
│ └── src/main/java/cn/itcast/feign
│ ├── clients/UserClient.java # @FeignClient("userservice")
│ ├── config/DefaultFeignConfiguration.java # 方式二:@Bean Logger.Level 在这里
│ └── pojo/User.java
└── user-service # 用户服务,8081
配置前缀是 feign.client.config,再下一层写服务名 或 default:
yaml
# order-service/src/main/resources/application.yml
feign:
client:
config:
userservice: # 只对 userservice 这个服务生效
loggerLevel: FULL # 日志级别, 取值 NONE/BASIC/HEADERS/FULL
default: # 用 default 代替服务名, 就是全局默认
loggerLevel: BASIC
logging:
level:
cn.itcast: debug # Feign 的日志由 cn.itcast 包下的 logger 输出, 这里必须放开
pattern:
dateformat: MM-dd HH:mm:ss:SSS
三点说明:
<服务名>就是@FeignClient(value = "...")里那个名字,这里必须写userservice而不是user-service或某个地址------写错了不会报错,静默不生效,这是排查时的第一嫌疑对象。- 想让所有 Feign 客户端都按同一级别来,把服务名换成
default。上面同时写了两段,userservice单独用 FULL、其余服务用 BASIC。 - 只改 yml 里这段配置不够,
logging.level.cn.itcast: debug一定要有。工程本身的 yml 里本来就带了这一行,所以视频里加完配置重启就能看到日志;换个没配过的工程,很容易在这里卡住。
改完配置要重启 order-service 才生效------它是启动时读取的,不是热更新。
方式二:Java 代码
Java 侧的写法是声明一个返回 Logger.Level 的 @Bean。类本身不加 @Component 之类的注解,靠 @EnableFeignClients 或 @FeignClient 显式引入:
java
package cn.itcast.feign.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel() {
// 日志级别: NONE / BASIC / HEADERS / FULL
return Logger.Level.BASIC;
}
}
注意 Logger 是 feign.Logger,不是 org.slf4j.Logger、也不是 java.util.logging.Logger,import 写错编译期就会报错。
这个类放哪儿、怎么挂上去,决定了它的作用域。
全局生效 ------挂到启动类的 @EnableFeignClients 上:
java
package cn.itcast.order;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.config.DefaultFeignConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
// defaultConfiguration 指定的配置类, 对所有 Feign 客户端生效
@EnableFeignClients(clients = UserClient.class,
defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
局部生效 ------挂到具体的 @FeignClient 上:
java
package cn.itcast.feign.clients;
import cn.itcast.feign.config.DefaultFeignConfiguration;
import cn.itcast.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// configuration 只对当前这一个 Feign 客户端生效
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
作用域坑:@Bean 放错包会变成全局
这是和 Ribbon 自定义 IRule 同一类问题,值得单独拎出来。
DefaultFeignConfiguration 位于 cn.itcast.feign.config,而启动类 OrderApplication 在 cn.itcast.order。@SpringBootApplication 默认只扫描启动类所在包及其子包,cn.itcast.feign.config 不在 扫描范围内,所以这个类里的 @Bean 不会被 Spring 当作普通配置加载,只在被 defaultConfiguration / configuration 引用时才生效------现在的作用域是可控的。
反过来的写法是这样的:
tree
cn.itcast.order
├── OrderApplication.java # @SpringBootApplication 扫描 cn.itcast.order 及子包
└── config
└── FeignConfig.java # ❌ @Bean Logger.Level 放在这里
java
package cn.itcast.order.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLogLevel() {
return Logger.Level.BASIC;
}
}
启动类在 cn.itcast.order,@ComponentScan 覆盖了 cn.itcast.order.config,这个 @Bean 会被当成全局 Bean 注册进容器,于是所有 Feign 客户端都会用上它 ,哪怕你原本只想给 userservice 开日志。想只对一个服务生效,就得像上面那样把配置类挪出扫描范围、再用 @FeignClient(configuration = ...) 精确指定------讲义里 Feign 客户端被抽取到 feign-api 模块、配置类放在 cn.itcast.feign.config,这个包结构本身就规避了该问题。
优先级
两种方式同时存在时怎么取?常见的理解口径是局部优先于全局,从高到低:
#mermaid-svg-3FM8YOPBzrJADcNZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3FM8YOPBzrJADcNZ .error-icon{fill:#552222;}#mermaid-svg-3FM8YOPBzrJADcNZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3FM8YOPBzrJADcNZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3FM8YOPBzrJADcNZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3FM8YOPBzrJADcNZ .marker.cross{stroke:#333333;}#mermaid-svg-3FM8YOPBzrJADcNZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3FM8YOPBzrJADcNZ p{margin:0;}#mermaid-svg-3FM8YOPBzrJADcNZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3FM8YOPBzrJADcNZ .cluster-label text{fill:#333;}#mermaid-svg-3FM8YOPBzrJADcNZ .cluster-label span{color:#333;}#mermaid-svg-3FM8YOPBzrJADcNZ .cluster-label span p{background-color:transparent;}#mermaid-svg-3FM8YOPBzrJADcNZ .label text,#mermaid-svg-3FM8YOPBzrJADcNZ span{fill:#333;color:#333;}#mermaid-svg-3FM8YOPBzrJADcNZ .node rect,#mermaid-svg-3FM8YOPBzrJADcNZ .node circle,#mermaid-svg-3FM8YOPBzrJADcNZ .node ellipse,#mermaid-svg-3FM8YOPBzrJADcNZ .node polygon,#mermaid-svg-3FM8YOPBzrJADcNZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3FM8YOPBzrJADcNZ .rough-node .label text,#mermaid-svg-3FM8YOPBzrJADcNZ .node .label text,#mermaid-svg-3FM8YOPBzrJADcNZ .image-shape .label,#mermaid-svg-3FM8YOPBzrJADcNZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-3FM8YOPBzrJADcNZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3FM8YOPBzrJADcNZ .rough-node .label,#mermaid-svg-3FM8YOPBzrJADcNZ .node .label,#mermaid-svg-3FM8YOPBzrJADcNZ .image-shape .label,#mermaid-svg-3FM8YOPBzrJADcNZ .icon-shape .label{text-align:center;}#mermaid-svg-3FM8YOPBzrJADcNZ .node.clickable{cursor:pointer;}#mermaid-svg-3FM8YOPBzrJADcNZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3FM8YOPBzrJADcNZ .arrowheadPath{fill:#333333;}#mermaid-svg-3FM8YOPBzrJADcNZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3FM8YOPBzrJADcNZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3FM8YOPBzrJADcNZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FM8YOPBzrJADcNZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3FM8YOPBzrJADcNZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FM8YOPBzrJADcNZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3FM8YOPBzrJADcNZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3FM8YOPBzrJADcNZ .cluster text{fill:#333;}#mermaid-svg-3FM8YOPBzrJADcNZ .cluster span{color:#333;}#mermaid-svg-3FM8YOPBzrJADcNZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3FM8YOPBzrJADcNZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3FM8YOPBzrJADcNZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-3FM8YOPBzrJADcNZ .icon-shape,#mermaid-svg-3FM8YOPBzrJADcNZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FM8YOPBzrJADcNZ .icon-shape p,#mermaid-svg-3FM8YOPBzrJADcNZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3FM8YOPBzrJADcNZ .icon-shape .label rect,#mermaid-svg-3FM8YOPBzrJADcNZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FM8YOPBzrJADcNZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3FM8YOPBzrJADcNZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3FM8YOPBzrJADcNZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} @FeignClient(configuration = X.class) 指定的配置
feign.client.config.<服务名> 的配置
@EnableFeignClients(defaultConfiguration = ...)
feign.client.config.default 的配置
Feign 内置默认值 (Level 为 NONE)
这个顺序是社区里常见的说法,用来指导"哪个会盖住哪个"足够,但具体行为随 Spring Cloud 版本有调整,落地时以所用版本文档为准,别当成铁律。
验证:日志长什么样
以视频里的操作为例:把 order-service 的 loggerLevel 配成 FULL,重启,清空控制台,浏览器访问一次 http://localhost:8088/order/101,再回来看 IDEA。除开 MyBatis 的 SQL 日志,会多出下面这样一段(不同 Feign 版本措辞和字段顺序略有差异):
text
2026-09-30 10:12:03:120 DEBUG 24680 --- [nio-8088-exec-3] c.i.f.clients.UserClient : [UserClient#findById] ---> GET http://userservice/user/1 HTTP/1.1
2026-09-30 10:12:03:121 DEBUG 24680 --- [nio-8088-exec-3] c.i.f.clients.UserClient : [UserClient#findById] ---> END HTTP (0-byte body)
2026-09-30 10:12:03:361 DEBUG 24680 --- [nio-8088-exec-3] c.i.f.clients.UserClient : [UserClient#findById] <--- HTTP/1.1 200 (241ms)
2026-09-30 10:12:03:361 DEBUG 24680 --- [nio-8088-exec-3] c.i.f.clients.UserClient : [UserClient#findById] content-type: application/json
2026-09-30 10:12:03:361 DEBUG 24680 --- [nio-8088-exec-3] c.i.f.clients.UserClient : [UserClient#findById] date: Wed, 30 Sep 2026 02:12:03 GMT
2026-09-30 10:12:03:362 DEBUG 24680 --- [nio-8088-exec-3] c.i.f.clients.UserClient : [UserClient#findById] <--- END HTTP (68-byte body)
日志里能读出几件事:---> 开头是请求段,带方法、URL、协议;<--- 是响应段,200 (241ms) 说明状态码正常、耗时 241 毫秒;头部字段逐行列出;最后是响应体字节数。这段日志的 logger 名就是 Feign 接口的全限定名 cn.itcast.feign.clients.UserClient,再次印证了 logging.level 得按包名放开。
换成 BASIC,上面只剩 ---> GET ... 和 <--- HTTP/1.1 200 (241ms) 两行,头信息、Body 全部消失------这也是为什么建议日常开发用 BASIC:够定位问题,又不至于把报文全刷出来。
调用的代码本身不用动,OrderService 照常注入 UserClient 调用即可:
java
package cn.itcast.order.service;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
// Feign 远程调用, 这里的调用过程就是日志里记录的那次 HTTP 请求
User user = userClient.findById(order.getUserId());
order.setUser(user);
return order;
}
}
实战坑清单
| 现象 | 原因 | 处理 |
|---|---|---|
| 配了 Feign 的 Level,还是没日志 | 没配 logging.level.cn.itcast: debug,或包的 logger 级别是 INFO |
确认接口所在包被 logging.level 覆盖且为 debug |
只改了 logging.level,依然没日志 |
Feign 的 Level 还是默认 NONE,没有生成日志内容 |
加上 feign.client.config.<服务名>.loggerLevel |
| 配了服务名但完全不生效,也不报错 | 服务名写错(如写成 user-service、USER-SERVICE) |
与 @FeignClient(value = "...") 逐字符核对 |
| 只想给一个服务开日志,结果全开了 | @Bean Logger.Level 所在包被 @ComponentScan 扫到 |
把配置类挪出启动类扫描范围,改用 configuration = X.class |
| 生产环境日志文件暴涨、出现手机号 token | 生产开着 FULL |
生产用 NONE,最多 BASIC;敏感报文只在本地短时开 |
| 改了 yml 不生效 | Feign 配置在启动时读取 | 重启服务 |
| 以为 Feign 一定不重试 | Feign 自身默认不重试,但底层 Ribbon 有重试机制 | 需要精确控制时配置 feign.Retryer |
API 速览
| 配置项 | 位置 | 作用域 | 说明 |
|---|---|---|---|
feign.client.config.<服务名>.loggerLevel |
application.yml |
单个服务 | 值是 NONE/BASIC/HEADERS/FULL |
feign.client.config.default.loggerLevel |
application.yml |
全局 | default 即所有 Feign 客户端 |
@Bean Logger.Level |
Java 配置类 | 由挂载点决定 | feign.Logger.Level,不是 slf4j 的 Logger |
@EnableFeignClients(defaultConfiguration = X.class) |
启动类 | 全局 | 配置类需在扫描范围外才有意义 |
@FeignClient(configuration = X.class) |
Feign 接口 | 单个服务 | 局部配置优先 |
logging.level.<包名> |
application.yml |
日志框架 | Feign 日志的落地闸门,必须为 debug |
官方文档
- Spring Cloud OpenFeign --- Feign logging
- Spring Cloud OpenFeign --- Overriding Feign Defaults
- OpenFeign Logger.Level Javadoc
总结
- Feign 可配置项有五类,日常真正会调的是
Logger.Level,其余保持默认。 - 日志四档里
NONE是默认值,所以没日志是正常现象;FULL会打请求体,生产慎用。 - Feign 的
Logger.Level决定"生成不生成日志",logging.level决定"输出不输出日志",两道闸门要一起开。 - 配置文件方式最省事:
feign.client.config.<服务名>.loggerLevel,default即全局。 - Java 代码方式要盯住作用域:
@Bean落在启动类扫描范围内就是全局,要局部生效得把配置类放到扫描范围外再显式挂载。 - 优先级上局部优先于全局,但具体行为随版本变化,以对应版本文档为准。