spring_cloud_feign_log 不念不忘少年蓝@ 2022-12-13 04:04 179阅读 0赞 这篇主要介绍一下`spring cloud feign log`的相关知识点。 下面以一个项目中的具体例子来说明: 下面是一个接口,在`a`服务中通过`feign`去调用`b`服务的`generateBizNo`接口,最后返回`b`服务的`generateBizNo`所返回的响应。 @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 cloud` 的`feign log`来打印`feign`接口调用日志,效果图如下: \[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siEil36M-1602235194448)(/Users/yongyongli/msad\_document/documents/image-20201009164140207.png)\] 通过上图我们可以看到`feign log`的日志输出有如下的信息: * 接口调用的方法及域名 * `http`协议 * 请求的头信息`content-type`以及`content-length` * 入参报文和相应报文,都是`json`格式 * 请求耗时以及响应的状态码 * 请求应用的名称以及端口号 下面是关于`spring cloud feign log`的相关知识点和如何才可以做出上面的效果: 首先,需要增加一个配置类: 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; } } 第二步,在你使用`@FeignClien`t的注解的时候,里面新增属性`configuration=FeignLogConfiguration.class` @FeignClient(value = "serviceName", url = "serviceUrl" , fallbackFactory = FeignFallbackFactory.class,configuration = FeignLogConfiguration.class ) 第三步,需要进行一些配置,我们使用的是nacos,所以在a服务的配置文件中新增如下的配置信息: logging: level: com: xxx: yyy: DEBUG 需要注意的是`logging`和`level`两个层级是必须配置的,其他层级就是包路径,此处是日志的级别,需要设置为`DEBUG`,才可以生效。 到这里,基本就可以实现上面的效果了。 接下来说一下相关的知识点: 该`Logger`是`feign`包下的一个抽象类,位于 <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> </dependency> 存在一个枚举`Level`,里面四个枚举项: * `NONE`:不打印日志 * `BASIC`:只打印请求方法和url,以及响应状态码和执行时间 * `FULL`:打印头信息,请求体,响应豹纹和元数据 * `HEADERS`:打印基本的请求和响应头信息 其中存在一个`logRequest`的方法用来处理请求,`logAndRebufferResponse`来处理响应报文。 具体的源码如下所示: 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 debug messages. Adapted from {@code retrofit.RestAdapter.Log}. */ public abstract class Logger { protected static String methodTag(String configKey) { return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('('))) .append("] ").toString(); } /** * Override to log requests and responses using your own implementation. Messages will be http * request and response text. * * @param configKey value of {@link Feign#configKey(Class, java.lang.reflect.Method)} * @param format {@link java.util.Formatter format string} * @param args arguments applied to {@code format} */ protected abstract void log(String configKey, String format, Object... args); protected void logRequest(String configKey, Level logLevel, Request request) { log(configKey, "---> %s %s HTTP/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, "---> END HTTP (%s-byte body)", bodyLength); } } protected void logRetry(String configKey, Level logLevel) { log(configKey, "---> RETRYING"); } protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, 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, "<--- END HTTP (%s-byte body)", bodyLength); return response.toBuilder().body(bodyData).build(); } else { log(configKey, "<--- END HTTP (%s-byte body)", bodyLength); } } return response; } protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) { log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(), elapsedTime); if (logLevel.ordinal() >= Level.FULL.ordinal()) { StringWriter sw = new StringWriter(); ioe.printStackTrace(new PrintWriter(sw)); log(configKey, sw.toString()); log(configKey, "<--- END ERROR"); } return ioe; } /** * Controls the level of logging. */ public enum Level { /** * No logging. */ NONE, /** * Log only the request method and URL and the response status code and execution time. */ BASIC, /** * Log the basic information along with request and response headers. */ HEADERS, /** * Log the headers, body, and metadata for both requests and responses. */ FULL } /** * Logs to System.err. */ public static class ErrorLogger extends Logger { @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 extends Logger { final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName()); @Override protected void logRequest(String configKey, Level logLevel, Request request) { if (logger.isLoggable(java.util.logging.Level.FINE)) { super.logRequest(configKey, logLevel, request); } } @Override protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException { if (logger.isLoggable(java.util.logging.Level.FINE)) { return super.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 configures java.util.logging to sanely log messages at FINE level without additional * formatting. */ public JavaLogger appendToFile(String logfile) { logger.setLevel(java.util.logging.Level.FINE); try { FileHandler handler = new FileHandler(logfile, true); handler.setFormatter(new SimpleFormatter() { @Override public String format(LogRecord record) { return String.format("%s%n", record.getMessage()); // NOPMD } }); logger.addHandler(handler); } catch (IOException e) { throw new IllegalStateException("Could not add file handler.", e); } return this; } } public static class NoOpLogger extends Logger { @Override protected void logRequest(String configKey, Level logLevel, Request request) { } @Override protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException { return response; } @Override protected void log(String configKey, String format, Object... args) { } } } 最后说一下整个的调用链路: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5MzQzMjU_size_16_color_FFFFFF_t_70_pic_center] 为什么配置`log`的级别为`debug`,需要看看`Slf4jLogger`类对`request`和`response`请求响应报文的处理。 关于`spring cloud feign log`的相关知识介绍到这里,如果本文存在不对之处,麻烦指点批评。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5MzQzMjU_size_16_color_FFFFFF_t_70_pic_center 1] [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5MzQzMjU_size_16_color_FFFFFF_t_70_pic_center]: /images/20221123/2ce1435544e64f108952ad4572711481.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5MzQzMjU_size_16_color_FFFFFF_t_70_pic_center 1]: /images/20221123/7098f88d44b34d5c86d8130571e81c28.png
还没有评论,来说两句吧...