文章目录
- 开篇
- 定位Feign是怎么打印日志的?
- 自定义FeignLogger实现
开篇
在上一篇Feign打印日志文章中,已经成功打印了@FeignClient请求服务的日志信息,但是默认打印的日志太过零散,不是我们想要的。怎么能自定义日志打印的格式和内容呢? 这篇文章将展示如何自定义Feign的日志。
定位Feign是怎么打印日志的?
首先定位Feign是在哪里打印的日志?怎么打印的?
开启Debug
feign.ReflectiveFeign.FeignInvocationHandler#invoke
方法
feign.SynchronousMethodHandler#invoke
方法
feign.SynchronousMethodHandler#executeAndDecode
方法
最终,定位到feign.SynchronousMethodHandler#executeAndDecode
方法中看到了打印日志的方法。并且看到了feign.Logger.Level==NONE
时不会打印日志。
进入feign.Logger#logRequest
方法
默认使用的是Slf4jLogger
对象.
知道了Feign是用Slf4jLogger
对象打印日志的,我们模仿Slf4jLogger
类自定义Logger对象,然后让Feign使用我们自定义的Logger对象不就可以了。
那么Feign是如何获取Slf4jLogger
? 通过debug定位到是通过org.springframework.cloud.openfeign.DefaultFeignLoggerFactory
创建的。
自定义FeignLogger实现
自定义Feign日志需要两步:
- 定义FeignLogger类继承
feign.Logger
,重新响应的方法,例如:logRequest
,logAndRebufferResponse
. - 定义CustomFeignLoggerFactory类实现
org.springframework.cloud.openfeign.FeignLoggerFactory
接口,实现create
方法。并通过@Component
将该类注册到spring容器中。
下面看具体代码实现:
public class FeignLogger extends Logger {
/**
* 默认打印body的大小(字节数)
*/
private static final Integer DEFAULT_LIMIT_BODY_SIZE = 1024;
private final ThreadLocal<Integer> localRequestHash = new ThreadLocal<>();
private Integer bodySize = DEFAULT_LIMIT_BODY_SIZE;
private final org.slf4j.Logger logger;
public FeignLogger() {
this(FeignLogger.class);
}
public FeignLogger(String name) {
this(LoggerFactory.getLogger(name));
}
public FeignLogger(Class<?> clazz) {
this(LoggerFactory.getLogger(clazz));
}
public FeignLogger(org.slf4j.Logger logger) {
this.logger = logger;
}
@Override
protected void logRequest(String configKey, Level logLevel, Request request) {
if (this.logger.isDebugEnabled()) {
final int hashCode = request.hashCode();
localRequestHash.remove();
localRequestHash.set(hashCode);
final String methodTag = methodTag(configKey);
final StringBuilder builder = new StringBuilder(methodTag);
builder.append(" request:{} => {} {}");
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
builder.append(" header:{}");
final Map<String, Collection<String>> headers = request.headers();
if (request.body() != null && logLevel.ordinal() >= Level.FULL.ordinal()) {
final int length = request.body().length;
String bodyText = getRequestBody(request, bodySize);
builder.append(" body:{} ({}-byte body)");
this.logger.debug(builder.toString(), hashCode, request.method(), request.url(), headers,
bodyText != null ? bodyText : "Binary data", length);
} else {
this.logger.debug( builder.toString(), hashCode, request.method(), request.url(), headers);
}
} else {
this.logger.debug( builder.toString(), hashCode, request.method(), request.url());
}
}
}
/**
* 获取requestBody
* @param request Request
* @param length 取值最大多少
* @return body
*/
private String getRequestBody(Request request, int length) {
return request.charset() != null ?
new String(Arrays.copyOf(request.body(), Math.min(request.body().length, length)), request.charset()) : null;
}
@Override
protected void logRetry(String configKey, Level logLevel) {
if (this.logger.isDebugEnabled()) {
final Integer localHash = this.localRequestHash.get();
final String methodTag = methodTag(configKey);
final String layout = methodTag + "request:{} ---> RETRYING";
logger.debug(layout, localHash);
}
}
@Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
if (this.logger.isDebugEnabled()) {
final String methodTag = methodTag(configKey);
final Integer localHash = localRequestHash.get();
final StringBuilder builder = new StringBuilder(methodTag);
builder.append(" response:{} => {}{}");
String reason = getReason(logLevel, response);
int status = response.status();
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
final Map<String, Collection<String>> headers = response.headers();
builder.append(" header:{}");
int bodyLength = 0;
if (isSuccessResponse(response, status) && logLevel.ordinal() >= Level.FULL.ordinal()) {
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
bodyLength = bodyData.length;
if (bodyLength > 0) {
builder.append(" body:{} ({}-byte body) ({}ms)");
this.logger.debug(builder.toString(), localHash, status, reason, headers,
getResponseBody(bodyData, bodySize), bodyLength, elapsedTime);
} else {
builder.append(" ({}-byte body) ({}ms)");
this.logger.debug(builder.toString(), localHash, status, reason, headers, bodyLength, elapsedTime);
}
return response.toBuilder().body(bodyData).build();
} else {
this.logger.debug(builder.toString(), localHash, status, reason, headers);
}
} else {
this.logger.debug(builder.toString(), localHash, status, reason);
}
}
localRequestHash.remove();
return response;
}
private String getReason(Level logLevel, Response response) {
return response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ? response.reason() : "";
}
private String getResponseBody(byte[] bodyData, Integer limitLength) {
return Util.decodeOrDefault(Arrays.copyOf(bodyData, Math.min(bodyData.length, limitLength)), Util.UTF_8,
"Binary data");
}
private boolean isSuccessResponse(Response response, int status) {
return response.body() != null && status != 204 && status != 205;
}
@Override
protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
final Integer localHash = localRequestHash.get();
localRequestHash.remove();
final String methodTag = methodTag(configKey);
String layout = methodTag + " error:{} => {}: {} ({}ms)";
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
StringWriter sw = new StringWriter();
ioe.printStackTrace(new PrintWriter(sw));
layout += System.getProperty("line.separator") + "{}";
this.logger.error(layout, localHash, ioe.getClass().getSimpleName(), ioe.getMessage(), elapsedTime, sw);
} else {
this.logger.error(layout, localHash, ioe.getClass().getSimpleName(), ioe.getMessage(), elapsedTime);
}
return ioe;
}
@Override
protected void log(String s, String s1, Object... objects) {
//
}
public Integer getBodySize() {
return bodySize;
}
public void setBodySize(Integer bodySize) {
this.bodySize = bodySize;
}
}
@Component
public class CustomFeignLoggerFactory implements FeignLoggerFactory {
@Override
public Logger create(Class<?> type) {
return new FeignLogger(type);
}
}
FeignLogger中对打印的请求体和响应体做了截取,最多只取前1024个字节。
打印示例:
2023-04-26 23:20:33.279 DEBUG 28178 --- [nio-9085-exec-2] com.szile.demo.auth.feign.api.ServerApi : [ServerApi#test] request:1354383157 => GET http://auth-server/test header:{}
2023-04-26 23:20:33.299 DEBUG 28178 --- [nio-9085-exec-2] com.szile.demo.auth.feign.api.ServerApi : [ServerApi#test] response:1354383157 => 200 header:{cache-control=[no-cache, no-store, max-age=0, must-revalidate], connection=[keep-alive], content-type=[application/json], date=[Wed, 26 Apr 2023 15:20:33 GMT], expires=[0], keep-alive=[timeout=60], pragma=[no-cache], transfer-encoding=[chunked], vary=[Access-Control-Request-Headers, Access-Control-Request-Method, Origin], x-content-type-options=[nosniff], x-frame-options=[DENY], x-xss-protection=[1; mode=block]} body:{"code":"00000","msg":"成功!","data":"192.168.0.104"} (57-byte body) (16ms)