这篇文章,我们主要介绍一下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 cloud的feign 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
需要注意的是logging和level两个层级是必须配置的,其他层级就是包路径,此处是日志的级别,需要设置为DEBUG,才可以生效~
到这里,基本就可以实现上面的效果了
接下来说一下相关的知识点: 该Logger是feign包下的一个抽象类,位于
代码语言: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的相关知识今天我们就先介绍到这里,如果本文存在不对之处,欢迎大家批评指正!