自定义Feign日志

news2024/9/20 16:26:08

文章目录

    • 开篇
    • 定位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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/466680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系统分析师《企业信息化战略与实施》高频知识点二

企业信息化战略与实施---系统建模 组织结构是一个企业内部部门的划分以及相互之间的关系&#xff0c;每个企业都有自己的组织结构图&#xff0c;它将企业分成若干部分&#xff0c;标明行政隶属关系。组织结构图是一种树结构&#xff0c;树的分支是根据上下级和行政隶属关系绘制…

二叉树 + 技巧

题目难度备注2471. 逐层排序二叉树所需的最少操作数目1635BFS 置换环 离散化2641. 二叉树的堂兄弟节点 II1677BFS 文章目录 周赛二叉树问题[2471. 逐层排序二叉树所需的最少操作数目](https://leetcode.cn/problems/minimum-number-of-operations-to-sort-a-binary-tree-by-l…

【AI绘画】云服务器部署stable-diffusion-webui保姆级教程

1.背景 之前给大家写过Mac苹果笔记本上部署stable-diffusion-webui的教程&#xff0c;知乎链接&#xff1a; 【奶奶看了也不会】AI绘画 Mac安装stable-diffusion-webui绘制AI妹子保姆级教程 但是安装过程就花了一天的时间&#xff0c;各种问题处理起来真是苦不堪言。。。而且…

如何解决生产缺料问题?

对于一个生产型企业来说&#xff0c;生产缺料是管理中的疑难问题之一。 生产缺料导致的最直接的危害就有两个&#xff1a; 第一就是不能准时交货&#xff0c;降低企业信用&#xff0c;情况严重可能导致根据合同赔款&#xff0c;甚至丢失客户。 第二个危害就是增加了生产成本…

部署LVS-DR 集群及实验

一、LVS-DR工作原理 LVS-DR&#xff08;Linux Virtual Server Director Server&#xff09;工作模式&#xff0c;是生产环境中最常用的一种工作模式。 #①LVS-DR 模式&#xff0c;Director Server 作为群集的访问入口&#xff0c;不作为网关使用&#xff1b; #②节点 Directo…

ueditor富文本编辑器上传木马图片或木马文件漏洞

漏洞分析&#xff1a; ueditor插件目录一般都自带index.html文件&#xff08;完整demo文件&#xff09;&#xff0c;这个文件原来是用来测试插件功能的&#xff0c;但粗心的程序员们&#xff0c;开发好项目后&#xff0c;都忘记删除这个demo文件了&#xff0c;导致黑客可以很轻…

微服务不是本地部署的最佳选择,不妨试试模块化单体

微服务仅适用于成熟产品 关于从头开始使用微服务&#xff0c;马丁・福勒&#xff08;Martin Fowler&#xff09;总结道&#xff1a; 1. 几乎所有成功的微服务都是从一个过于庞大而不得不拆分的单体应用开始的。 2. 几乎所有从头开始以微服务构建的系统&#xff0c;最后都会因…

win11安装双系统ubuntu20.04指导

目录 一、制作U盘启动盘二、硬盘分区2.1方法2.2分区过程 三、安装系统3.1进入U盘启动3.2安装ubuntu3.3设置启动项 四、更新软件五、遇到的问题5.1不能连接WIFI 电脑型号&#xff1a;联想拯救者Y7000P 2023 无线网卡型号&#xff1a;WIFI 6E AX211 160MHz 系统版本&#xff1a;w…

借助尾号限行 API 实现限行规则应用的设计思路分析

引言 尾号限行是指根据车牌号的末尾数字&#xff0c;规定某些时段内不能在特定区域行驶&#xff0c;这是城市交通管理的一种措施。尾号限行政策的实施可以缓解城市交通拥堵问题&#xff0c;减少环境污染和交通事故等问题。 尾号限行 API 是一种提供已知所有执行限行政策的城市…

iSulad+Kuasar:管理面资源消耗锐减 99%的新一代统一容器运行时解决方案

随着云计算和容器技术的不断发展&#xff0c;容器引擎和容器运行时已经成为了云原生时代的基石&#xff0c;它们负责了容器生命周期的管理以及容器运行过程中环境的创建和资源的配置。openEuler 社区基于容器引擎项目 iSulad[1]在解决容器运行效率、安全性以及隔离性等问题上进…

DVWD-Command Injection Low/Medium/High低中高级别

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 Command Injection 一、Low级别二、Medium级别三、High级别 命令注入这关是一个Ping测试功…

《港联证券》半导体复苏预期“抢跑”产业现实 细分市场缓慢回温

虽然说没有一个冬季不可逾越&#xff0c;但本轮“抢跑”的半导体复苏预期&#xff0c;不得不继续面对工业缓慢复苏的实际。 自2022年下半年以来&#xff0c;资本商场上半导体复苏接棒“缺芯”成为关注焦点&#xff0c;部分半导体职业个股迅速收复跌幅&#xff0c;刷新阶段高点&…

Apache ActiveMQ 远程代码执行漏洞 (CVE-2016-3088)复现

当前漏洞环境部署在vulhub,当前验证环境为vulhub靶场&#xff08;所有实验均为虚拟环境&#xff09; 实验环境&#xff1a;攻击机----kali 靶机&#xff1a;centos7 1、进入环境cd activemq/CVE-2016-3088/&#xff08;靶机&#xff09; 2、启动环境&#xff1a;docker-compos…

设备健康管理软件如何帮助企业优化设备维保计划?

基于AI和工业互联网技术的新型设备管理系统&#xff0c;可以通过实时监测设备运行状态、预测潜在故障、提供预防性维护建议等方式&#xff0c;实现设备管理的数字化和智能化。该类设备管理系统的核心功能一般包括设备状态监测、故障预测、预防性维护、故障知识库管理等&#xf…

Java字节码指令

Java代码运行的过程是Java源码->字节码文件(.class)->运行结果。 Java编译器将Java源文件&#xff08;.java&#xff09;转换成字节码文件(.class)&#xff0c;类加载器将字节码文件加载进内存&#xff0c;然后进行字节码校验&#xff0c;最后Java解释器翻译成机器码。 …

第六届中国软件开源创新大赛——飞桨赛题新鲜出炉,速来pick!

最近想要充个电&#x1f50b; 飞桨邀你开启开源贡献之旅 寻找那个最“会”的你 顶级开源项目、资深研发指导、高阶开发者合作交流&#xff0c;‍‍ Buff 叠满&#xff01; 技能提升、丰富简历、高额奖金&#xff0c; 你还不心动&#xff1f; 赛事简介 中国软件开源创新大赛已成…

聊聊StarRocks向量化执行引擎-过滤操作

聊聊StarRocks向量化执行引擎-过滤操作 StarRocks是开源的新一代极速MPP数据库&#xff0c;采用全面向量化技术&#xff0c;充分利用CPU单核资源&#xff0c;将单核执行性能做到极致。本文&#xff0c;我们聊聊过滤操作是如何利用SIMD指令进行向量化操作。 过滤操作的SIMD向量化…

值得拥有的一篇SpringBoot入门基础指南

目录 一. 创建SpringBoot项目1.1 使用Spring Initializr快速构建项目1.2 手动创建springboot项目 二. SpringBoot入门案例解析2.1 依赖管理特性2.2 starter场景启动器2.3 引导类自动配置 三. REST风格四. 配置文件 一. 创建SpringBoot项目 1.1 使用Spring Initializr快速构建项…

「数据架构」MDM实现失败的主要原因

我经常参与一个组织的MDM程序&#xff0c;当他们在一个失败的项目之后向InfoTrellis请求帮助进行清理&#xff0c;或者开始尝试X&#xff0c;以实现对某些人来说非常困难的目标时。主数据管理实现失败的原因有很多&#xff0c;但是没有一个是由于在这些场景中使用的责备游戏的原…

【Redis】Redis缓存

目录 一、缓存 1、概念 2、作用 3、缺点 二、缓存模型 三、缓存的更新 1、更新策略 2、主动更新的三种模式 1.cache aside pattern 2.read/write through pattern 3.write behind caching pattern 3、线程安全问题 1.缓存删除还是更新缓存 2.先删除缓存后操作数据…