自定义Feign日志

开篇

在上一篇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)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超人@不会飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值