前言

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

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

①  using(var client= new HttpClient()) 调用Dispose()方法,并不会很快释放底层Socket连接, 同时新建Socket需要时间,这在高并发场景下Socket耗尽。  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.LogicalHandlerSystem.Net.Http.HttpClient.bce-request.ClientHandler 日志头即是来自LoggingScopeHttpMessageHandler ,LoggingHttpMessageHandler 两个处理器,

 给出手绘的UML类图: 

 

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

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

编程实践

   如以上分析,

P1  实现 IHttpMessageHandlerFilter接口,在接口中移除默认的两个日志处理器;

复制代码
    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.