spring cloud feign log 实践

2022-07-12 14:19:56 浏览数 (1)

这篇文章,我们主要介绍一下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的相关知识今天我们就先介绍到这里,如果本文存在不对之处,欢迎大家批评指正!

0 人点赞