Java框架 SpringCloud 快速入门: Feign 的自定义配置与日志级别

概述

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

官方文档

总结

  • Feign 可配置项有五类,日常真正会调的是 Logger.Level,其余保持默认。
  • 日志四档里 NONE 是默认值,所以没日志是正常现象;FULL 会打请求体,生产慎用。
  • Feign 的 Logger.Level 决定"生成不生成日志",logging.level 决定"输出不输出日志",两道闸门要一起开。
  • 配置文件方式最省事:feign.client.config.<服务名>.loggerLevel,default 即全局。
  • Java 代码方式要盯住作用域:@Bean 落在启动类扫描范围内就是全局,要局部生效得把配置类放到扫描范围外再显式挂载。
  • 优先级上局部优先于全局,但具体行为随版本变化,以对应版本文档为准。
相关推荐
成旭先生1 小时前
企业全景信息查询 API:工商照面 33 项、股东出资、变更与社保一次查全
java·开发语言·api接口·企业信息查询·工商数据·风控尽调·供应商准入
2601_962885721 小时前
批量取数比循环单取快多少?用 AlphaFeed klines.batch给多股票取数提速
开发语言·php·batch
动词ing1 小时前
【C语言题目练习】算法 整数反转
c语言·开发语言·算法
多弗朗皮卡丘2 小时前
C++多继承
开发语言·c++·多继承
她的男孩2 小时前
分片上传的大文件人人可下载:文件模块 isPrivate 在合并时被抹成 false,另有 4 个静默坑
java·后端·架构
lie..2 小时前
30天从零开始学AI应用开发(Day 19):项目二完结:RAG 知识库问答系统,把自己攒的资料变成私人顾问
开发语言·人工智能·python
SWAGGY..2 小时前
【C++进阶】:(7)红黑树的原理与 C++ 实现:结构设计、插入调整及性质验证
android·java·开发语言·c++·算法
(Charon)2 小时前
【C++面试】堆内存与栈内存:string、vector和对象到底存在哪里
开发语言·c++·面试
Wang's Blog2 小时前
Java框架 SpringCloud 快速入门: Eureka 服务发现与服务名调用改造
java·spring cloud·eureka