HttpClientFactory日志不好用,自己扩展一个?

前言

  .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

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

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

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

相关文章

树、二叉树简介

一、树的定义 树是由n&#xff08;n>1&#xff09;个有限节点组成一个具有层次关系的集合&#xff0c;它有如下特点&#xff1a; 1、每个节点有零个或多个子节点&#xff1b; 2、没有父节点的节点称为根节点&#xff1b; 3、每一个非根节点有且只有一个父节点&#xff1b; 4…

Quartz.net定时任务的使用及获取正在运行的JOB

定时任务管理类实现了如下功能&#xff1a;1、对定时任务进行管理 2、创建定时任务&#xff0c;需要给定时任务一个job的名称 3、判断给定的job的任务是否已存在 4、停止定时任务的功能namespace MyUKD.Quartz{ public class QuartzSchedulerMgr { private static readonly ILo…

漫谈认证与授权

漫谈认证与授权Intro认证与授权一直以来都是很多人在讨论的话题&#xff0c;之所以想这次谈一谈认证和授权&#xff0c;主要是因为最近看到许多文章都把认证和授权混为一谈&#xff0c;把认证方式当作是授权方式。所以想写篇文章谈谈我眼中的认证与授权。Authentication什么是认…

【译】gRPC vs HTTP APIs

本文翻译自 ASP.NET Blog | gRPC vs HTTP APIs&#xff0c;作者 James&#xff0c;译者 Edison Zhou。现在&#xff0c;ASP.NET Core使开发人员可以构建gRPC服务。gRPC是一个远程过程调用框架&#xff0c;专注于高性能和开发人员的生产力。ASP.NET Core 3.0中集成了gRPC&#x…

.NET Core 3.0 的新改进:针对分布式应用程序的故障诊断和监控

由于分布式应用是由多个组件组成的&#xff0c;且这些组件往往是由不同的团队拥有和操作&#xff0c;所以在与应用程序发生交互时&#xff0c;就会需要跨多个组件执行代码的分布式跟踪。如果用户遇到了问题&#xff0c;想要确定是哪个组件出现了差错&#xff0c;基本就是一件不…

【翻译】.NET Core3.1发布

.NET Core3.1发布我们很高兴宣布.NET Core 3.1的发布。实际上&#xff0c;这只是对我们两个多月前发布的.NET Core 3.0的一小部分修复和完善。最重要的是.NET Core 3.1是长期支持&#xff08;LTS&#xff09;版本&#xff0c;并且将支持三年。和过去一样&#xff0c;我们希望花…

JVM(1)——JVM内存分区

一、JVM简介 JVM&#xff0c;即Java虚拟机&#xff08;Java Virtual Machine&#xff09;&#xff0c;一种能够运行Java bytecode的虚拟机&#xff0c;是Java实现跨平台的基础。 引入Java语言虚拟机后&#xff0c;Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚…

使用Azure Pipelines从GitHub发布NuGet包

[本文目录]ps: 阅读本文大概需要20分钟欢迎大家点击上方公众号链接关注我&#xff0c;了解新西兰码农生活什么是 YAML?name/value 名称/值collections 集合multiple data types 复合数据类型comments 注释Pipelines 的 YAML 结构在 Azure DevOps Pipelines 中创建第一个任务为…

JVM(2)——JVM类加载机制

一、JVM类加载机制简介 虚拟机把描述类的数据从Class文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的Java类型&#xff0c;这就是虚拟机的类加载机制。 在Java语言里面&#xff0c;类型的加载和连接过程都是在程序运…

PYPL 12月榜单发布,编程语言、IDE与数据库市场如何?

PYPL&#xff08;PopularitY of Programming Language&#xff0c;编程语言流行指数&#xff09;12 月份的榜单已经发布了。PYPL 是非常流行的参考指标&#xff0c;其榜单数据的排名均是根据榜单对象在 Google 上相关的搜索频率进行统计排名&#xff0c;原始数据来自 Google Tr…

JVM(3)——JVM类加载器

一、类加载器简介 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现&#xff0c;以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。 类加载器虽然只用于实现类的…

.NET Core应用框架AA介绍(二)

AA的开源地址https://github.com/ChengLab/AAFrameWork AA框架是一个基础应用框架&#xff0c;是建立在众多大家熟知的流行工具之上并与之集成。比如&#xff1a;ASP.NET Core、Automapper、Dapper、Dapper-FluentMap、RabbitMQ、Redis、MassTransit、Log4net等等大家可以很方便…

JVM(4)——对象访问

一、对象创建过程在Java语言中&#xff0c;对象是如何访问的呢&#xff1f;对象访问在Java语言中无处不在&#xff0c;是最普通的程序行为&#xff0c;但即使是最简单的访问&#xff0c;也会涉及Java虚拟机栈、Java堆区、方法区。 对于下面这行代码&#xff0c; Object obj ne…

鹅厂后台开发工程师的工作日常

写在前面 &#xff1a;本故事纯属虚构&#xff0c;如有雷同&#xff0c;不负责任。为了整理 Linux 开发和日常使用的常用命令&#xff0c;想了好几天才串了这么个故事。虽然有点牵强&#xff0c;但是内容还是挺干的~欢迎大家点评。在很久很久以前&#xff0c;鹅厂开发类工程师职…

.NET Core开发的iNeuOS工业互联网平台,发布 iNeuDA 数据分析展示组件,快捷开发图形报表和数据大屏...

经过一段时间的努力&#xff0c;iNeuDA产品组件已经开发和测试完成&#xff0c;现在正式上线。现在iNeuOS工业互联网操作系统的技术体系和产品体系更佳完善&#xff0c;为中小企业提供更佳全面解决方案。如下图&#xff1a;iNeuDA 一站式大数据分析平台作为国内领先的新一代自助…

asp.net core 从 3.0 到 3.1

asp.net core 从 3.0 到 3.1Intro今天 .net core 3.1 正式发布了&#xff0c;.net core 3.1 正式版已发布&#xff0c;3.1 主要是对 3.0 的 bug 修复&#xff0c;以及一些小优化&#xff0c;而且作为 LTS 版本&#xff0c;建议大家升级。值得一提的是.net core 2.2 这个月就要寿…

身边的设计模式(三):抽象工厂 与 依赖注入

上篇文章&#xff0c;我们说到了简单工厂和工厂方法&#xff0c;如果没看过的&#xff0c;请先看上篇&#xff0c;不然的话&#xff0c;可能有些吃力&#xff0c;或者直接点击阅读原文&#xff0c;查看我博客园的对应详细版的文章。大家学到了这里&#xff0c;我建议自己可以练…

Java基础知识——Java集合详解

数组是Java很常见的一种数据结构&#xff0c;能够快速地进行存取。但是当遇到下面几种情况&#xff1a; ①我们需要存储的数据集数目是不定的 ②我们希望数据集能够自动排序 ③我们需要以键值对的方式存储数据 … 数组就不能满足我们的需求了。这时候&#xff0c;我们就需要使用…

边缘计算与云计算的不同,这篇说明白了!

术语“边缘计算”是指一种分布式计算&#xff0c;是将数据存储和计算带到需要它的站点或设备附近&#xff0c;这种分配设置消除了滞后时间并节省了带宽。与“物联网”相比&#xff0c;这是一种针对云环境的优化方法。它在数据源附近&#xff08;即网络的“边缘”&#xff09;处…

经典排序算法(12)——总结

一、排序算法简介 排序算法&#xff08;Sorting algorithm&#xff09;是一种能将一串数据&#xff0c;依照特定排序方式&#xff08;依照其中的某个或某些关键字的大小&#xff09;进行排列的一种算法。 常见的排序算法有&#xff1a;交换排序&#xff08;冒泡排序、快速排序&…