SpringCloud 源码系列之全局 Fegin 日志收集(okHttpClient、httpClient)

news2025/2/28 6:55:13

SpringCloud 源码系列之全局 Fegin 日志收集(okHttpClient、httpClient)目录

    • HttpClient 全局日志收集思路
    • 切换成HttpClient验证配置效果
    • HttpClient 全局日志收集源码分析
    • 看源码顺带产物
    • okHttpClient 全局日志收集
    • 总结

接上文SpringCloud OpenFegin 底层是如何切换HttpClient 说起,今天玩点实际的应用,SpringCloud 全局 Fegin 日志收集。

  1. Spring Cloud OpenFegin(创建、发送请求)源码
  2. SpringCloud 之HttpClient、HttpURLConnection、OkHttpClient切换源码
    在这里插入图片描述

HttpClient 全局日志收集思路

由于通过编写 yaml配置的方式无法实现HttpClient 全局日志收集的需求,因为他不能配置全局的拦截器。因此我们需要自定义配置

feign:
  compression:
    response:
      enabled: true
  httpclient:
    connection-timeout: 777
    time-to-live: 666
    enabled: true

上文已经说过,当我们的项目中引入了HttpClient 的 maven 依赖后,SpringCloud 默认会加载HttpClient 的配置类,如下图。如果项目中自定义了HttpClient,那么优先加载自定义配置,反之加载Cloud 默认的HttpClient 类。

这里温习一下 LoadBalancerFeignClient 类的作用:Cloud 发起网络请求的入口,真正干活的是里面的 delegate。
在这里插入图片描述
知道了原理后,我们直接在common 模块中加上自定义的HttpClient 的配置就好了,代码如下,里面需要注意的是,打印日志的时候我们需要对InputStream的副本进行操作,不然后续的业务接受方,将结果序列化成 json 返回给前端,这个过程发现IO流关闭了,就会报IO流关闭的错误。

package com.xxy.system.common.config;

import com.xxy.system.common.interceptor.OkHttpCLientInterceptor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.apache.http.*;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StreamUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class FeignConfig{
    @Bean
    public CloseableHttpClient customHttpClient() {
        HttpRequestInterceptor requestInterceptor = new HttpRequestInterceptor() {
            @Override
            public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
                log.info("URL:{},params:{}", ((HttpRequestWrapper) httpRequest).getOriginal(), StreamUtils.copyToString(((HttpEntityEnclosingRequest) httpRequest).getEntity().getContent(), Charset.defaultCharset()));
            }
        };
        HttpResponseInterceptor responseInterceptor = new HttpResponseInterceptor() {
            @Override
            public void process(HttpResponse response, HttpContext httpContext) throws HttpException, IOException {
                InputStream inputStream = response.getEntity().getContent();
                ByteArrayOutputStream baosOutputStream = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) > -1) {
                    baosOutputStream.write(buffer, 0, len);
                }
                baosOutputStream.flush();
                InputStream body = new ByteArrayInputStream(baosOutputStream.toByteArray());
                log.info(StreamUtils.copyToString(body, StandardCharsets.UTF_8));
                inputStream.close();
                body.close();
                //设置 InputStream 副本,给后续请求
                BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
                basicHttpEntity.setContent(new ByteArrayInputStream(baosOutputStream.toByteArray()));
                response.setEntity(basicHttpEntity);
            }
        };
        CloseableHttpClient build = HttpClients.custom()
                .setMaxConnTotal(666)
                .addInterceptorFirst(requestInterceptor)
                .addInterceptorFirst(responseInterceptor)
                .build();
        return build;
    }

}

对应的 yaml 配置
在这里插入图片描述

切换成HttpClient验证配置效果

在org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient的构造方法中打一个断点,可以看到里面的属性 maxTotal=666,是我们的自定义的HttpClient,说明配置成功了。在这里插入图片描述
在这里插入图片描述
随便发起一个 fegin 请求,入参、请求真实的 url、返回结果通通给打印出来了。
在这里插入图片描述

HttpClient 全局日志收集源码分析

来到org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration类,HttpClientFeignConfiguration是Cloud 默认的HttpClient 的配置类。查看他上面的条件注解

@ConditionalOnMissingBean({CloseableHttpClient.class}) ,当容器中不存在CloseableHttpClient的BeanDefinition时,那么此配置类生效,反正不生效。所以当我们的项目配置了HttpClient 后,Cloud 默认的HttpClient 配置类就会失效。

在这里插入图片描述
这里顺便提一嘴:@ConditionalOnMissingBean 的原理:因为 @ConditionalOnMissingBean 是被自动装配进去的。普通的被@Configuration标注的类,被扫描的时机早于自动装配的类,因此当我们项目中注入自定义的HttpClient 后,@ConditionalOnMissingBean会生效。

看源码顺带产物

feign.compression.response.enabled配置源码位置org.springframework.cloud.openfeign.clientconfig。HttpClientFeignConfiguration。字面意思理解,Cloud 默认是不压缩返回结果,开启了feign.compression.response.enabled=true 后就开启压缩。源码如下图。
在这里插入图片描述

okHttpClient 全局日志收集

过程和如何切换成HttpClient 一样,加入自定义配置即可。

  @Bean
    public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
        Integer maxTotalConnections = httpClientProperties.getMaxConnections();
        Long timeToLive = httpClientProperties.getTimeToLive();
        TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
        return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
    }

    @Bean
    public OkHttpClient okClient(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
        Boolean followRedirects = httpClientProperties.isFollowRedirects();
        Integer connectTimeout = httpClientProperties.getConnectionTimeout();
        Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
        return httpClientFactory.createBuilder(disableSslValidation)
                .connectTimeout((long) connectTimeout, TimeUnit.MILLISECONDS)
                .followRedirects(followRedirects)
                .connectionPool(connectionPool)
                .readTimeout(666, TimeUnit.SECONDS)
                .addInterceptor(new OkHttpCLientInterceptor()) // 自定义请求日志拦截器
                .build();
    }

加好后debug 看到配置已经成功了,后续打印日志过程在拦截器中实现
在这里插入图片描述
okHttpClient 日志拦截器

/**
 * fegin 日志打印
 */
@Slf4j
public class OkHttpCLientInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        log.info("#request#url:{} #body:{}", chain.request().url(), convertRequest(chain.request()));
        Response response = chain.proceed(chain.request());
        BufferedSource source = response.body().source();
        source.request(Long.MAX_VALUE);
        //创建 io 流副本,否则会出现 io 流提前关闭的异常
        log.info("#reponse#url:{} #body:{}", chain.request().url(), source.buffer().clone().readString(Charset.forName("UTF-8")));
        return response;
    }

    private String convertRequest(final Request request) {
        final Request copy = request.newBuilder().build();
        final Buffer buffer = new Buffer();
        try {
            copy.body().writeTo(buffer);
        } catch (IOException e) {
            return "something error,when show requestBody";
        }
        return buffer.readUtf8();
    }
}

总结

通过阅读okHttpClient、httpClient的默认创建源码,了解到,如果需要自定义okHttpClient、httpClient时,项目中注入即可。同时打印日志的时候,注意用流副本操作,否则会出现 io 流异常关闭的问题。

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

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

相关文章

使用Python+pygame实现贪吃蛇小游戏

使用Pythonpygame贪吃蛇小游戏 使用第三方库pygame,关于Python中pygame游戏模块的安装使用可见 https://blog.csdn.net/cnds123/article/details/119514520 给出两种实现。 第一种 运行效果如下: 游戏源码如下: import pygame import sy…

中国互联网的早期形态

1 大约是从 1991 年开始,国内开始了第一个 BBS 站——北京长城站,经过长时间发展,直到 1995 年,随着计算机及其外设的大幅降价,BBS 才逐渐被部分人们所认识。少数玩 BBS 站的“极客”站长, 基于个人关系&am…

Linux搭建和使用redis

官网地址:http://redis.io/download 文件上传到服务器 tar包解压 tar zxvf redis-5.0.14.tar.gz安装 进入解压目录下,找到Makefile所在目录,执行make命令 make执行之后,会产生src等目录,进入执行make install命令…

23111 网络编程 day2

思维导图 重打代码 #include<myhead.h> #define SER_IP "192.168.122.150" //服务器ip #define SER_PORT 8888 //服务器端口int main(int argc, const char *argv[]) {//1.创建用于连接的套接字int sfdsocket(AF_INET,SOCK_STREAM,0);if(sfd-1){perror("…

【图形学】探秘图形学奥秘:区域填充的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《图形学 | 图像解码》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 &#x1f30c;1. 初识模式识别 …

Python - 深夜数据结构与算法之 位运算

目录 一.引言 二.位运算简介 1.二进制与十进制 2.左/右移 3.位运算 4.异或 XOR 5.指定位置的位运算 6.实战要点 三.经典算法实战 1.Number-1-of-bits [191] 2.Power-Of-Two [231] 3.Reverse-2-Bits [190] 4.N-Queens [51] 四.总结 一.引言 通常情况下我们计数采…

Java SE入门及基础(14)

二重循环 1. 什么是二重循环 二重循环就是一个循环结构中又包含另外一个循环结构 while ( 外层循环条件 ){ //外层循环操作 while ( 内层循环条件 ){ //内层循环操作 } //外层循环操作 } while ( 外层循环条件 ){ //外层循环操作 for ( 循环变量初始化 ; 内层循环条…

模拟瑞幸小程序购物车

是根据渡一袁老师的大师课写的&#xff0c;如有什么地方存在问题&#xff0c;还请大家指出来哟ど⁰̷̴͈꒨⁰̷̴͈う♡&#xff5e; index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-e…

论文阅读笔记AI篇 —— Transformer模型理论+实战 (二)

论文阅读笔记AI篇 —— Transformer模型理论实战&#xff08;二&#xff09; 第二遍阅读&#xff08;通读&#xff09;2.1 Background2.2 Model Architecture2.2.1 Encoder and Decoder Stacks2.2.2 Scaled Dot-Product Attention2.2.3 Multi-Head Attention 2.3 Why Self-Atte…

python 元组的详细用法

当前版本&#xff1a; Python 3.8.4 文章目录如下 1. 介绍元组 2. 定义元组 3. 访问元组 4. 查询元组 1. 介绍元组 元组&#xff08;Tuple&#xff09;是一个有序的、不可变的数据序列。它可以包含各种类型的数据&#xff0c;例如数字、字符串、列表等。元组使用圆括号()来…

机器人制作开源方案 | 红外热成像巡检小车

作者&#xff1a;马跃宁、赵婷婷、牟金晶、邢艳慧、隋鹏飞 单位&#xff1a;哈尔滨剑桥学院 指导老师&#xff1a;韩轶男、齐丹丹 1. 项目简介 1.1 项目背景 21世纪以来&#xff0c;随着我国先进水平不断提高&#xff0c;家家户户用电也是我们必不可少的一部分&#xff0c;…

Tuxera2024版本正式上线!(免费mac读写磁盘工具)

当您获得一台新 Mac 时&#xff0c;它只能读取 Windows NTFS 格式的 USB 驱动器。要将文件添加、保存或写入您的 Mac&#xff0c;您需要一个附加的 NTFS 驱动程序。Tuxera 的 Microsoft NTFS for Mac 是一款易于使用的软件&#xff0c;可以在 Mac 上打开、编辑、复制、移动或删…

centos7 arm服务器编译安装python 3.8

前言 CentOS (Community Enterprise Operating System) 是一种基于 Red Hat Enterprise Linux (RHEL) 进行源代码再编译并免费提供给用户的 Linux 操作系统。 CentOS 7 采用了最新的技术和软件包&#xff0c;并提供了强大的功能和稳定性。它适用于各种服务器和工作站应用场景&a…

golang文件相对路径问题

目录结构 2.具体代码&#xff1a; const dataFile "../data/data.json"_, fileName, _, _ : runtime.Caller(1)dataPath : path.Join(path.Dir(fileName), dataFile)fmt.Println(dataPath)// open filefile, err : os.Open(dataPath)if err ! nil {log.Fatalln(err…

【GitHub】如何删除GitHub仓库里的文件夹(区分 rm/git rm)

删除GitHub仓库里的一个文件夹 1、复制仓库地址2、在本地新建一个空文件夹3、在空文件夹内&#xff0c;右键选择Git Bash Here4、弹出GIT Bash框5、克隆远程仓库6、拉取远程仓库7、查看仓库里的文件8、选择想要删除的文件夹进行删除9、提交删除说明10、更新GitHub远程仓库 在gi…

高校教务系统登录页面JS分析——河北地质大学

高校教务系统密码加密逻辑及JS逆向 本文将介绍高校教务系统的密码加密逻辑以及使用JavaScript进行逆向分析的过程。通过本文&#xff0c;你将了解到密码加密的基本概念、常用加密算法以及如何通过逆向分析来破解密码。 本文仅供交流学习&#xff0c;勿用于非法用途。 一、密码加…

【小笔记】时序数据分类算法最新小结

2024.1.15 最近基于时序数据训练分类算法&#xff0c;对其进行了一番了解&#xff0c;主要围绕以下几点&#xff1a; 时序数据算法有哪些细分类&#xff1f;时序数据分类算法经典模型&#xff1f;当下时序分类算法模型强baseline&#xff1f;有没有现成的工具&#xff1f; 1…

unity面试题

一&#xff1a;什么是协同程序&#xff1f; 在主线程运行的同时开启另一段逻辑处理&#xff0c;来协助当前程序的执行&#xff0c;协程很像多线程&#xff0c;但是不是多线程&#xff0c;Unity的协程实在每帧结束之后去检测yield的条件是否满足。 二&#xff1a;Unity3d中的碰…

快速更改flutter已有项目的项目名称和id等

如果你使用了别人已有的仓库模板或者想更改现有项目的名称&#xff0c;是一件非常繁琐的工作&#xff0c;需要修改全平台的文件还是相当麻烦的&#xff0c;所以这里推荐一个小工具&#xff0c;可以帮助大家快速实现更改项目名称的目的&#xff0c;这个工具地址&#xff1a;rena…

2019年认证杯SPSSPRO杯数学建模B题(第二阶段)外星语词典全过程文档及程序

2019年认证杯SPSSPRO杯数学建模 基于统计和迭代匹配的未知语言文本片段提取模型 B题 外星语词典 原题再现&#xff1a; 我们发现了一种未知的语言&#xff0c;现只知道其文字是以 20 个字母构成的。我们已经获取了许多段由该语言写成的文本&#xff0c;但每段文本只是由字母…