【问题标题】:Feign Client request and response and URL LoggingFeign 客户端请求和响应以及 URL 日志记录
【发布时间】:2019-04-11 04:25:25
【问题描述】:

如何记录 Feign 客户端请求、响应和 URL 的负载。我必须实现拦截器吗?因为我的要求是将请求和响应记录在数据库的特殊表上。

【问题讨论】:

    标签: logging request interceptor feign


    【解决方案1】:

    Feign 具有开箱即用的日志记录机制,只需简单的步骤即可实现。

    如果你使用的是 spring-cloud-starter-feign

    使用Slf4jLogger 进行日志记录。Feign logging documentation

    根据文档,可以配置以下日志记录级别,

    • NONE - 无日志记录(默认)。
    • BASIC - 仅记录请求方法和 URL 以及响应状态代码和执行时间。
    • HEADERS - 记录基本信息以及请求和响应标头。
    • FULL - 记录请求和响应的标头、正文和元数据。

    注入Logger.Level bean 就足够了。

        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.BASIC;
        }
    

    如果您更喜欢使用配置属性来配置所有@FeignClient,您可以使用默认的假名创建配置属性。

    feign:
      client:
        config:
          default:
            loggerLevel: basic
    

    如果您使用的是'io.github.openfeign:feign-core'

    如果您正在构建 Feign 构建器,那么您可以提及 logLevel(Level.BASIC) 作为

    Feign.builder()
        .logger(new Slf4jLogger())
        .logLevel(Level.BASIC)
        .target(SomeFeignClient.class, url);
    

    我们可以灵活地自定义日志消息

    默认的 feign 请求和响应日志记录

    Request logging

    Resopnse logging

    我们可以通过重写 Logger#logRequestLogger#logAndRebufferResponse 方法来自定义 feign 请求、响应日志记录模式。在以下示例中,我们自定义了请求日志记录模式

    log(configKey, "---> %s %s HTTP/1.1 (%s-byte body) ", request.httpMethod().name(), request.url(), bodyLength);
    

    和响应记录模式

    log(configKey, "<--- %s %s HTTP/1.1 %s (%sms) ", request.httpMethod().name(), request.url(), status, elapsedTime);
    

    完整的例子是

    
    import feign.Logger;
    import feign.Request;
    import feign.Response;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    
    import static feign.Logger.Level.HEADERS;
    
    @Slf4j
    public class CustomFeignRequestLogging extends Logger {
    
        @Override
        protected void logRequest(String configKey, Level logLevel, Request request) {
    
            if (logLevel.ordinal() >= HEADERS.ordinal()) {
                super.logRequest(configKey, logLevel, request);
            } else {
                int bodyLength = 0;
                if (request.requestBody().asBytes() != null) {
                    bodyLength = request.requestBody().asBytes().length;
                }
                log(configKey, "---> %s %s HTTP/1.1 (%s-byte body) ", request.httpMethod().name(), request.url(), bodyLength);
            }
        }
    
        @Override
        protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
                throws IOException {
            if (logLevel.ordinal() >= HEADERS.ordinal()) {
                super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
            } else {
                int status = response.status();
                Request request = response.request();
                log(configKey, "<--- %s %s HTTP/1.1 %s (%sms) ", request.httpMethod().name(), request.url(), status, elapsedTime);
            }
            return response;
        }
    
    
        @Override
        protected void log(String configKey, String format, Object... args) {
            log.debug(format(configKey, format, args));
        }
    
        protected String format(String configKey, String format, Object... args) {
            return String.format(methodTag(configKey) + format, args);
        }
    }
    
    

    注意: 请求有效负载可以通过轻松记录

    String bodyText =
                  request.charset() != null ? new String(request.body(), request.charset()) : null;
    

    但在读取输入流Util.toByteArray(response.body().asInputStream()) 后要小心编写响应有效负载,然后您必须像response.toBuilder().body(bodyData).build() 一样再次构造响应。否则,你最终会得到期望。原因是响应流被读取并且总是在返回之前关闭,这就是为什么该方法被命名为 logAndRebufferResponse

    如何使用自定义CustomFeignRequestLogging

    如果你只使用 'io.github.openfeign:feign-core' 构建 feign 客户端

    Feign.builder()
         .logger(new CustomFeignRequestLogging())
         .logLevel(feign.Logger.Level.BASIC);
    
    

    如果您使用的是'org.springframework.cloud:spring-cloud-starter-openfeign'

    @Configuration
    public class FeignLoggingConfiguration {
    
        @Bean
        public CustomFeignRequestLogging customFeignRequestLogging() {
            return new CustomFeignRequestLogging();
        }
    
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.BASIC;
        }
    }
    
    

    【讨论】:

    • 感谢您提供的详细信息,这对我很有帮助。
    • 如何将 CustomFeignRequestLogging 插入到 feign 配置中?
    • @ManojKumarS,好问题,我已经更新了答案,说明如何使用自定义 CustomFeignRequestLogging?
    【解决方案2】:

    Feign 客户端响应没有拦截器。请求拦截器仅适用于 Feign 客户端。

    最好的解决方案是使用 RestTemplate 而不是 Feign:

    @Configuration
    public class RestConfiguration {
        @Bean
        public RestTemplate restTemplate() {
            RestTemplate restTemplate
                    = new RestTemplate(
                    new BufferingClientHttpRequestFactory(
                            new SimpleClientHttpRequestFactory()
                    )
            );
    
            List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
            if (CollectionUtils.isEmpty(interceptors)) {
                interceptors = new ArrayList<>();
            }
            interceptors.add(new UserRestTemplateClientInterceptor());
            restTemplate.setInterceptors(interceptors);
            return restTemplate;
        }
    
    }
    

    以及您想要使用的 restTemplate 的 @Autowire 如下:

    @Autowire
    RestTemplate restTemplate;
    

    【讨论】:

      【解决方案3】:

      Feign 提供了一个Logger 接口,可以记录完整的请求和响应。您需要在 Feign Builder 或 Configuration 中设置Logger.Level

      Feign.builder()
         .logLevel(Logger.Level.FULL) // this will log the request and response
         .target(MyApi, "my host");
      

      【讨论】:

      • 谢谢凯文。是的,你说得对,但我想在数据库的特殊表上记录请求和响应。
      • @RamiNassar,这不在您最初的问题中。我建议您使用该信息更新您的原始问题,或者提出另一个更具体的问题。
      【解决方案4】:

      在您的 RestConfiguration 中,您需要提升默认级别的日志记录 feignClient 并由 @Bean feignLogger 覆盖,例如:

      @Configuration(proxyBeanMethods = false)
      @EnableCircuitBreaker
      @EnableFeignClients(basePackageClasses = [Application::class])
      class RestConfiguration: WebMvcConfigurer {
      
          @Bean
          fun feignLoggerLevel(): Logger.Level {
              return Logger.Level.FULL
          }
      
          @Bean
          fun feignLogger(): Logger {
              return FeignClientLogger()
          }
      }
      

      并根据需要实现您的记录器。例如以日志格式登录:

      import feign.Logger
      import feign.Request
      import feign.Response
      import feign.Util.*
      import org.slf4j.LoggerFactory
      
      class FeignClientLogger : Logger() {
          private val log = LoggerFactory.getLogger(this::class.java)
      
          override fun logRequest(configKey: String?, logLevel: Level?, request: Request?) {
              if (request == null)
                  return
      
              val feignRequest = FeignRequest()
              feignRequest.method = request.httpMethod().name
              feignRequest.url = request.url()
              for (field in request.headers().keys) {
                  for (value in valuesOrEmpty(request.headers(), field)) {
                      feignRequest.addHeader(field, value)
                  }
              }
      
              if (request.requestBody() != null) {
                  feignRequest.body = request.requestBody().asString()
              }
      
              log.trace(feignRequest.toString())
          }
      
          override fun logAndRebufferResponse(
              configKey: String?,
              logLevel: Level?,
              response: Response?,
              elapsedTime: Long
          ): Response? {
              if (response == null)
                  return response
      
              val feignResponse = FeignResponse()
              val status = response.status()
              feignResponse.status = response.status()
              feignResponse.reason =
                  (if (response.reason() != null && logLevel!! > Level.NONE) " " + response.reason() else "")
              feignResponse.duration = elapsedTime
      
              if (logLevel!!.ordinal >= Level.HEADERS.ordinal) {
                  for (field in response.headers().keys) {
                      for (value in valuesOrEmpty(response.headers(), field)) {
                          feignResponse.addHeader(field, value)
                      }
                  }
      
                  if (response.body() != null && !(status == 204 || status == 205)) {
                      val bodyData: ByteArray = toByteArray(response.body().asInputStream())
                      if (logLevel.ordinal >= Level.FULL.ordinal && bodyData.isNotEmpty()) {
                          feignResponse.body = decodeOrDefault(bodyData, UTF_8, "Binary data")
                      }
                      log.trace(feignResponse.toString())
      
                      return response.toBuilder().body(bodyData).build()
                  } else {
                      log.trace(feignResponse.toString())
                  }
              }
              return response
          }
      
          override fun log(p0: String?, p1: String?, vararg p2: Any?) {}
      }
      
      class FeignResponse {
          var status = 0
          var reason: String? = null
          var duration: Long = 0
          private val headers: MutableList<String> = mutableListOf()
          var body: String? = null
      
          fun addHeader(key: String?, value: String?) {
              headers.add("$key: $value")
          }
      
          override fun toString() =
              """{"type":"response","status":"$status","duration":"$duration","headers":$headers,"body":$body,"reason":"$reason"}"""
      }
      
      class FeignRequest {
          var method: String? = null
          var url: String? = null
          private val headers: MutableList<String> = mutableListOf()
          var body: String? = null
      
          fun addHeader(key: String?, value: String?) {
              headers.add("$key: $value")
          }
      
          override fun toString() =
              """{"type":"request","method":"$method","url":"$url","headers":$headers,"body":$body}"""
      }
      

      【讨论】:

        猜你喜欢
        • 2011-02-01
        • 2021-10-16
        • 2018-12-20
        • 1970-01-01
        • 1970-01-01
        • 2017-08-07
        • 2021-11-15
        • 2013-04-04
        • 1970-01-01
        相关资源
        最近更新 更多