前言

  .NetCore2.1新推出HttpClientFactory工厂类, 替代了早期的HttpClient,并新增了弹性Http调用机制 (集成Policy组件)。

替换的初衷还是简单说下:

①  using(var client= new HttpClient()) 调用Dispose()方法,并不会很快释放底层Socket连接,同时新建Socket需要时间,这在高并发场景下Socket耗尽。 传送门

②  由于①很多人会想到用单例或静态类构建HttpClient实例,但是这里还有一个坑,HttpClient会忽略DNS的变化。 传送门

HttpClientFactory 以一种模块化、可命名、弹性可预期的方式重建了HttpClient的使用方式。HttpClientFactory以依赖注入的方式集成到.NETCore 框架:

 HttpClientFactory典型用法

使用时从IHttpClientFactory工厂创建所需HttpClient实例,发起业务端请求。

观察Info级别日志:

19/12/04 11:06:46 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[18}].[]Start processing HTTP request GET http://localhost:5000/v1/eqid/b827a9400004132a000000065dc26470 19/12/04 11:06:46 [Info].[System.Net.Http.HttpClient.bce-request.ClientHandler].[18}].[]Sending HTTP request GET http://localhost:5000/v1/eqid/b827a9400004132a000000065dc26470 19/12/04 11:06:46 [Info].[System.Net.Http.HttpClient.bce-request.ClientHandler].[34}].[]Received HTTP response after 174.5088ms - OK 19/12/04 11:06:46 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[34}].[]End processing HTTP request after 211.1478ms - OK

一切都是那么自然、优雅。

头脑风暴

  观察上面单次请求的日志,由外层LogicHandler和内层ClientHandler 日志头组成。这样的日志可以想象到有2个问题:

① 在高并发使用HttpClient,日志条数众多,没有类似TraceId 这样的机制定位 某次HttpClient调用的完整日志。

②  若是微服务/ 分布式调用,可能还有 将本次HttpClient调用日志与后置api日志 结合分析的需求, 这个日志也支持不了。

因此本文打算重新构建 HttpClientFactory日志:给某次请求的全部日志设置TraceId

结合我给出的典型用法来看IHttpClientFactory组件原理:

示例中System.Net.Http.HttpClient.bce-request.LogicalHandler,System.Net.Http.HttpClient.bce-request.ClientHandler 日志头即是来自LoggingScopeHttpMessageHandler ,LoggingHttpMessageHandler 两个处理器。

给出手绘的UML类图:

本次要扩展的入口便是IHttpMessageHandlerFilter接口,核心是自定义DelegatingHandler日志处理器。

编程实践

如以上分析,使用aspNetCore2.2需要做如下扩展:

P1

实现IHttpMessageHandlerFilter接口,移除接口中默认的2个日志处理器

public class TraceIdLoggingMessageHandlerFilter : IHttpMessageHandlerBuilderFilter    {        private readonly ILoggerFactory _loggerFactory;

        public TraceIdLoggingMessageHandlerFilter(ILoggerFactory loggerFactory)        {            _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));        }

        public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)        {            if (next == null)            {                throw new ArgumentNullException(nameof(next));            }

            return (builder) =>            {                // Run other configuration first, we want to decorate.                next(builder);

                var outerLogger =_loggerFactory.CreateLogger($"System.Net.Http.HttpClient.{builder.Name}.LogicalHandler");                builder.AdditionalHandlers.Clear();                builder.AdditionalHandlers.Insert(0,new CustomLoggingScopeHttpMessageHandler(outerLogger));            };        }    }

P2

实现带有TraceId能力的HttpClient 日志处理器,并加入到IHttpMessageHandlerFilter接口实现类

public class CustomLoggingScopeHttpMessageHandler : DelegatingHandler    {        private readonly ILogger _logger;

        public CustomLoggingScopeHttpMessageHandler(ILogger logger)        {            _logger = logger ?? throw new ArgumentNullException(nameof(logger));        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,            CancellationToken cancellationToken)        {            if (request == null)            {                throw new ArgumentNullException(nameof(request));            }

            using (Log.BeginRequestPipelineScope(_logger, request))            {                Log.RequestPipelineStart(_logger, request);                var response = await base.SendAsync(request, cancellationToken);                Log.RequestPipelineEnd(_logger, response);

                return response;            }        }

        private static class Log        {            private static class EventIds            {                public static readonly EventId PipelineStart = new EventId(100, "RequestPipelineStart");                public static readonly EventId PipelineEnd = new EventId(101, "RequestPipelineEnd");            }

            private static readonly Func<ILogger, HttpMethod, Uri, string, IDisposable> _beginRequestPipelineScope =                LoggerMessage.DefineScope<HttpMethod, Uri, string>(                    "HTTP {HttpMethod} {Uri} {CorrelationId}");

            private static readonly Action<ILogger, HttpMethod, Uri, string, Exception> _requestPipelineStart =                LoggerMessage.Define<HttpMethod, Uri, string>(                    LogLevel.Information,                    EventIds.PipelineStart,                    "Start processing HTTP request {HttpMethod} {Uri} [Correlation: {CorrelationId}]");

            private static readonly Action<ILogger, HttpStatusCode,string,Exception> _requestPipelineEnd =                LoggerMessage.Define<HttpStatusCode,string>(                    LogLevel.Information,                    EventIds.PipelineEnd,                    "End processing HTTP request - {StatusCode}, [Correlation: {CorrelationId}]");

            public static IDisposable BeginRequestPipelineScope(ILogger logger, HttpRequestMessage request)            {                var correlationId = GetCorrelationIdFromRequest(request);                return _beginRequestPipelineScope(logger, request.Method, request.RequestUri, correlationId);            }

            public static void RequestPipelineStart(ILogger logger, HttpRequestMessage request)            {                var correlationId = GetCorrelationIdFromRequest(request);                _requestPipelineStart(logger, request.Method, request.RequestUri, correlationId, null);            }

            public static void RequestPipelineEnd(ILogger logger, HttpResponseMessage response)            {                var correlationId = GetCorrelationIdFromRequest(response.RequestMessage);                _requestPipelineEnd(logger, response.StatusCode, correlationId, null);            }

            private static string GetCorrelationIdFromRequest(HttpRequestMessage request)            {                string correlationId = Guid.NewGuid().ToString();                if (request.Headers.TryGetValues("X-Correlation-ID", out var values))                    correlationId = values.First();                else                    request.Headers.Add("X-Correlation-ID",correlationId);                return correlationId;            }        }    }

以上TraceId的实现思路,参考了我前一篇博文《被忽略的TraceId,可以用起来了》的思路,为每次HttpClient调用过程设定  全局唯一的GUID标记, 后置api服务可酌情修改以上代码处理。》其中写入日志的代码Copy自HttpClientFactory源代码。

P3

在DI框架中替换原有的IHttpMessageHandlerFilter实现

services.Replace(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, TraceIdLoggingMessageHandlerFilter>());

发起两次HttpClient请求, 输出的日志如下:

19/12/04 12:59:22 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[17}].[]Start processing HTTP request GET http://localhost:5000/v1/eqid/ad78deef00444ed7000000035de704e8 [Correlation: 03de676d-680e-4a92-aef5-749bcc3ba499]19/12/04 12:59:22 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[4}].[]End processing HTTP request - OK, [Correlation: 03de676d-680e-4a92-aef5-749bcc3ba499]19/12/04 12:59:48 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[17}].[]Start processing HTTP request GET http://localhost:5000/v1/eqid/8ea0c3b66b60f0ff100000005de704fb [Correlation: 6f14393a-3a2b-45c4-a9b4-0b4ab874ef1d]19/12/04 12:59:48 [Info].[System.Net.Http.HttpClient.bce-request.LogicalHandler].[42}].[]End processing HTTP request - OK, [Correlation: 6f14393a-3a2b-45c4-a9b4-0b4ab874ef1d]

可以看到每次请求的开始和结束都带上了设定的GUID TraceId。

Tip

① 这个TraceId 可以使用你业务上独具一格的标记,这样在排查时, 能根据上游业务更好的追踪日志。

② 目前这个TraceId位于LogMessage,实际上可以为nlog自定义LogoutRenderer,将该TraceId放在显著位置,便于ETL等日志集成框架过滤。

That's All, 这是本次解决HttpClient日志无法追踪的思路和方案,思考+实践+UML制图,希望能给大家一些启发。

+ https://github.com/aspnet/HttpClientFactory/blob/master/src/Microsoft.Extensions.Http/Logging/LoggingHttpMessageHandlerBuilderFilter.cs

扫一扫左边二维码,
更多惊喜干货等着你。
............

HttpClientFactory日志不好用,自己扩展一个?相关推荐

  1. 粥做得好不好,全凭一个良心!

    粥做得好不好,全凭一个良心! 工作不也是凭良心么! 文章中的红色字体表达了一切! 霜降节气的到来,意味着冬季的临近,而霜降后气温的降低,利于蔬菜的淀粉沉淀,收浆后的莲藕也变得更为甘甜可口.在南京,就有 ...

  2. 婆媳关系不好首先就有一个斤斤计较的婆婆

    婆媳关系不好首先就有一个斤斤计较的婆婆,你如果和媳妇计较就失去了一个慈祥老人的根本.我妈妈和我嫂子计较,不让我给我嫂子的孙子钱.我说妈妈,嫂子都在帮我们刘家带孩子.她都在帮咱们,咱们的后代咱们应该不应 ...

  3. 为XV6系统扩展一个系统调用需要修改的文件

    为XV6系统扩展一个系统调用需要修改的文件 根据实际需求,会有一定的改变 defs.h sysproc.c syscall.h usys.S syscall.c user.h

  4. 伤感日志_爱情还有另外一个名字叫寂寞

    伤感日志_爱情还有另外一个名字叫寂寞 - 伤感日志_爱情还有另外一个名字叫寂寞 看惯了别人的分分合合 看惯了别人的死去活来 - 十八岁以前,所有美好的爱情憧憬 被现实撞击的粉碎粉碎 终于有了十八岁,才 ...

  5. Leader每天996,绩效被打C!CTO说,团队带不好,原因只有一个

    来源| 技术领导力(ID:jishulingdaoli) 熟悉老K的读者知道,老K也是从技术Leader一步步做到总监.VP的,也经历过:团队带不好,下属培养不起来,年底拿不出亮眼成绩等等问题.当然了 ...

  6. 怎样用java编写日志_用JAVA写一个日志类程序以供大家学习

    中华网络安全联盟    作者:jacoo    来源:本站原创    时间:2006-4-18 说明: 尽管JAVA类库和其他工具提供了不少的纪录程序运行状态的日志类,我发觉也 不是万能的,有时需要根 ...

  7. JavaWeb项目中如何扩展一个Request对象——包装器HttpServletRequestWrapper

    一.使用场景 在一个JavaWeb中我们会遇到统一处理出入参或者处理特殊参数的场景,这些场景就需要我们扩展我们的Request对象.所谓的包装器就是在原来的基础上包装一下就是在原来功能上添加一些其他功 ...

  8. php 加入日志功能,php怎么写一个日志功能的函数

    我们要写一个写日志的函数,首先需要了解需求,我们一般怎么用日志函数呢?例如,程序执行到某一步,我希望把这个变量(地址)$user_address的值打印到日志,我们希望日志里是这么写的: `xx-xx ...

  9. java tomcat 日志分析工具_设计一个Tomcat访问日志分析工具

    常使用web服务器的朋友大都了解,一般的web server有两部分日志: 一是运行中的日志,它主要记录运行的一些信息,尤其是一些异常错误日志信息 二是访问日志信息,它记录的访问的时间,IP,访问的资 ...

最新文章

  1. 【编译原理】让我们来构建一个简单的解释器(Let’s Build A Simple Interpreter. Part 2.)(python/c/c++版)(笔记)
  2. Hardware assisted virtualization and data execution protection must be enabled in the BIOS
  3. 五、Requests库详细的用法
  4. struts.xml文件的配置说明
  5. 好想学python下载_Python | 从零开始学(1)
  6. windbg工具安装配置及dump抓取
  7. 与非CCR代码互操作
  8. 【二分】【中等难度】noip模拟赛 聪哥的工资
  9. 题目243-交换输出
  10. CentOS配置Nginx官方的Yum源 及yum安装php
  11. 简单几步:教你利用NAS设备存储云盒子企业网盘的所有数据
  12. 【网页截图亲测可用】Linux + python3 + selenium + chrome + chrome-driver 服务器端网页截图
  13. php csv文件转json,php csv如何转json
  14. 安卓编程基础——列表
  15. 《勒索软件防护发展报告(2022年)》正式发布,助力企业高效应对勒索软件攻击
  16. gin 【日志记录】每天一个日志文件
  17. javascript组合模式创建对象
  18. IOS Appstore 预览图尺寸
  19. 科学家研发独特AI算法:用WiFi监测你是否在做梦
  20. 虚幻浏览器插件 加载透明网页

热门文章

  1. 在Cocos2d中实现能够惯性拖动的选择界面
  2. Teams内嵌的卡片image的限制
  3. 如何在Android Wear上节省电池寿命
  4. 小程序调用阿里云身份证识别OCR(附带七牛云上传图片)
  5. Pressed状态和clickable,duplicateParentState的关系
  6. 阿里云MaxCompute香港开服 将引入更多人工智能服务
  7. 媒体应用大数据,先解决三大难题
  8. POJ 2135 最小费用最大流
  9. ecs使用脚本安装oracle
  10. RHEL5.1下安装GCC