前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring cloud feign log 实践

spring cloud feign log 实践

作者头像
xdd
发布2022-07-12 14:19:56
4220
发布2022-07-12 14:19:56
举报
文章被收录于专栏:java技术鸡汤java技术鸡汤

这篇文章,我们主要介绍一下spring cloud feign log的相关知识点~ 我们以具体项目中的实例来做以下说明:

下面是一个接口,在a服务中通过feign去调用b服务的generateBizNo接口,最后返回b服务的generateBizNo所返回的响应;

代码语言:javascript
复制
@FeignClient(value = "serviceName", url ="serviceUrl"
        , fallbackFactory =FeignFallbackFactory.class,configuration = FeignLogConfiguration.class
)
public interface IOuterXXService extends BaseFeignClient {

    @RequestMapping(value ="/xx/yy", method = RequestMethod.POST)
    ResultBase<String>generateBizNo(XXX var1);
}

那么,如何来打印出这个feign调用的接口的相关日志呢?

  • 使用log去该接口的实现类的方法调用开始和结束打印日志?
  • 使用切面去打印日志?
  • 还有其他?

在这里我介绍的是使用spring cloudfeign log来打印feign接口调用日志,效果图如下:

以上我们可以看到feign log的日志输出有如下的信息:

  • 接口调用的方法及域名
  • http协议
  • 请求的头信息content-type以及content-length
  • 入参报文和相应报文,都是json格式
  • 请求耗时以及响应的状态码
  • 请求应用的名称以及端口号

下面我们介绍下spring cloud feign log的相关知识点;以及如何才可以做出上面的效果: 首先,我们需要增加一个配置类:

代码语言:javascript
复制
package com.xxx.xxx.xxx.spi.configuration;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author lee
 * @date 2020/10/9
 *
 **/
@Configuration
public class FeignLogConfiguration {

    @Bean
    Logger.Level feignLoggerLevel(){
                // 设置feign打印的log级别,此处的full代表的是信息全部打印,此log level非彼log level
        return Logger.Level.FULL;
    }
}

第二步,在你使用@FeignClient的注解的时候,里面新增属性configuration=FeignLogConfiguration.class

代码语言:javascript
复制
@FeignClient(value = "serviceName", url ="serviceUrl"
        , fallbackFactory =FeignFallbackFactory.class,configuration = FeignLogConfiguration.class
)

第三步,需要进行一些配置,我们使用的是nacos,所以在a服务的配置文件中新增如下的配置信息:

代码语言:javascript
复制
logging:
  level:
    com:
      xxx:
        yyy: DEBUG

需要注意的是logginglevel两个层级是必须配置的,其他层级就是包路径,此处是日志的级别,需要设置为DEBUG,才可以生效~

到这里,基本就可以实现上面的效果了

接下来说一下相关的知识点: 该Loggerfeign包下的一个抽象类,位于

代码语言:javascript
复制
<dependency>
 <groupId>io.github.openfeign</groupId>
 <artifactId>feign-core</artifactId>
</dependency>

存在一个枚举Level,里面四个枚举项:

  • NONE:不打印日志
  • BASIC:只打印请求方法和url,以及响应状态码和执行时间
  • FULL:打印头信息,请求体,响应豹纹和元数据
  • HEADERS:打印基本的请求和响应头信息

其中存在一个logRequest的方法用来处理请求logAndRebufferResponse来处理响应报文; 具体的源码如下所示:

代码语言:javascript
复制
package feign;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.FileHandler;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;

import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;
import static feign.Util.valuesOrEmpty;

/**
 * Simple logging abstraction for debugmessages.  Adapted from {@coderetrofit.RestAdapter.Log}.
 */
public abstract class Logger {

  protected static StringmethodTag(String configKey) {
    return newStringBuilder().append('[').append(configKey.substring(0,configKey.indexOf('(')))
        .append("]").toString();
  }

  /**
   * Override to log requests andresponses using your own implementation. Messages will be http
   * request and response text.
   *
   * @param configKey value of {@linkFeign#configKey(Class, java.lang.reflect.Method)}
   * @param format    {@link java.util.Formatter formatstring}
   * @param args      arguments applied to {@code format}
   */
  protected abstract void log(StringconfigKey, String format, Object... args);

  protected void logRequest(StringconfigKey, Level logLevel, Request request) {
    log(configKey, "---> %s %sHTTP/1.1", request.method(), request.url());
    if (logLevel.ordinal() >=Level.HEADERS.ordinal()) {

      for (String field :request.headers().keySet()) {
        for (String value :valuesOrEmpty(request.headers(), field)) {
          log(configKey, "%s:%s", field, value);
        }
      }

      int bodyLength = 0;
      if (request.body() != null) {
        bodyLength = request.body().length;
        if (logLevel.ordinal() >=Level.FULL.ordinal()) {
          String
              bodyText =
              request.charset() != null ?new String(request.body(), request.charset()) : null;
          log(configKey, "");// CRLF
          log(configKey, "%s",bodyText != null ? bodyText : "Binary data");
        }
      }
      log(configKey, "---> ENDHTTP (%s-byte body)", bodyLength);
    }
  }

  protected void logRetry(StringconfigKey, Level logLevel) {
    log(configKey, "--->RETRYING");
  }

  protected ResponselogAndRebufferResponse(String configKey, Level logLevel, Responseresponse,
                                           long elapsedTime) throws IOException {
    String reason = response.reason() !=null && logLevel.compareTo(Level.NONE) > 0 ?
        " " + response.reason(): "";
    int status = response.status();
    log(configKey, "<--- HTTP/1.1%s%s (%sms)", status, reason, elapsedTime);
    if (logLevel.ordinal() >=Level.HEADERS.ordinal()) {

      for (String field :response.headers().keySet()) {
        for (String value :valuesOrEmpty(response.headers(), field)) {
          log(configKey, "%s:%s", field, value);
        }
      }

      int bodyLength = 0;
      if (response.body() != null&& !(status == 204 || status == 205)) {
        // HTTP 204 No Content"...response MUST NOT include a message-body"
        // HTTP 205 Reset Content"...response MUST NOT include an entity"
        if (logLevel.ordinal() >=Level.FULL.ordinal()) {
          log(configKey, "");// CRLF
        }
        byte[] bodyData =Util.toByteArray(response.body().asInputStream());
        bodyLength =bodyData.length;
        if (logLevel.ordinal() >=Level.FULL.ordinal() && bodyLength > 0) {
          log(configKey, "%s",decodeOrDefault(bodyData, UTF_8, "Binary data"));
        }
        log(configKey, "<--- ENDHTTP (%s-byte body)", bodyLength);
        returnresponse.toBuilder().body(bodyData).build();
      } else {
        log(configKey, "<--- ENDHTTP (%s-byte body)", bodyLength);
      }
    }
    return response;
  }

  protected IOExceptionlogIOException(String configKey, Level logLevel, IOException ioe, longelapsedTime) {
    log(configKey, "<--- ERROR%s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),
        elapsedTime);
    if (logLevel.ordinal() >=Level.FULL.ordinal()) {
      StringWriter sw = newStringWriter();
      ioe.printStackTrace(newPrintWriter(sw));
      log(configKey,sw.toString());
      log(configKey, "<--- ENDERROR");
    }
    return ioe;
  }

  /**
   * Controls the level of logging.
   */
  public enum Level {
    /**
     * No logging.
     */
    NONE,
    /**
     * Log only the request method andURL and the response status code and execution time.
     */
    BASIC,
    /**
     * Log the basic information alongwith request and response headers.
     */
    HEADERS,
    /**
     * Log the headers, body, andmetadata for both requests and responses.
     */
    FULL
  }

  /**
   * Logs to System.err.
   */
  public static class ErrorLogger extendsLogger {
    @Override
    protected void log(String configKey,String format, Object... args) {
     System.err.printf(methodTag(configKey) + format + "%n",args);
    }
  }

  /**
   * Logs to the category {@link Logger}at {@link java.util.logging.Level#FINE}, if loggable.
   */
  public static class JavaLogger extendsLogger {

    final java.util.logging.Logger
        logger =
       java.util.logging.Logger.getLogger(Logger.class.getName());

    @Override
    protected void logRequest(StringconfigKey, Level logLevel, Request request) {
      if(logger.isLoggable(java.util.logging.Level.FINE)) {
        super.logRequest(configKey,logLevel, request);
      }
    }

    @Override
    protected ResponselogAndRebufferResponse(String configKey, Level logLevel, Responseresponse,
                                             long elapsedTime) throws IOException {
      if(logger.isLoggable(java.util.logging.Level.FINE)) {
        returnsuper.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
      }
      return response;
    }

    @Override
    protected void log(String configKey,String format, Object... args) {
      if(logger.isLoggable(java.util.logging.Level.FINE)) {
        logger.fine(String.format(methodTag(configKey)+ format, args));
      }
    }

    /**
     * Helper that configuresjava.util.logging to sanely log messages at FINE level without additional
     * formatting.
     */
    public JavaLogger appendToFile(Stringlogfile) {
      logger.setLevel(java.util.logging.Level.FINE);
      try {
        FileHandler handler = newFileHandler(logfile, true);
        handler.setFormatter(newSimpleFormatter() {
          @Override
          public String format(LogRecordrecord) {
            returnString.format("%s%n", record.getMessage()); // NOPMD
          }
        });
        logger.addHandler(handler);
      } catch (IOException e) {
        throw newIllegalStateException("Could not add file handler.", e);
      }
      return this;
    }
  }

  public static class NoOpLogger extendsLogger {

    @Override
    protected void logRequest(StringconfigKey, Level logLevel, Request request) {
    }

    @Override
    protected ResponselogAndRebufferResponse(String configKey, Level logLevel, Responseresponse,
                                             long elapsedTime) throws IOException {
      return response;
    }

    @Override
    protected void log(String configKey,String format, Object... args) {
    }
  }
}

最后说一下整个的调用链路:

为什么配置log的级别为debug,需要看看Slf4jLogger类对request和response请求响应报文的处理~ 关于spring cloud feign log的相关知识今天我们就先介绍到这里,如果本文存在不对之处,欢迎大家批评指正!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java技术鸡汤 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档