《ASP.NET Core 6框架揭秘》实例演示[26]:跟踪应用接收的每一次请求

很多人可能对ASP.NET Core框架自身记录的诊断日志并不关心,其实这些日志对纠错排错和性能监控提供了很有用的信息。如果需要创建一个APM(Application Performance Management)系统来监控ASP.NET Core应用处理请求的性能及出现的异常,我们完全可以将HostingApplication对象记录的日志作为收集的原始数据。实际上,目前很多APM(如OpenTelemetry.NET 、Elastic APM和SkyWalking APM等)针对都是利用这种方式收集分布式跟踪日志的。[本文节选《ASP.NET Core 6框架揭秘》第17章]

[S1701]ASP.NET针对请求的诊断日志(源代码)
[S1702]收集DiagnosticSource输出的日志(源代码)
[S1703]收集EventSource输出的日志(源代码)

[S1701]ASP.NET针对请求的诊断日志

为了确定什么样的信息会被作为诊断日志记录下来,我们通过一个简单的实例演示将HostingApplication对象写入的诊断日志输出到控制台上。HostingApplication对象会将相同的诊断信息以三种不同的方式进行记录,其中包含第8章“诊断日志(中篇)”介绍的日志系统。如下的演示程序利用WebApplicationBuilder的Logging属性得到返回的ILoggingBuilder对象,并调用它的AddSimpleConsole扩展方法为默认注册的ConsoleLoggerProvider开启了针对日志范围的支持。我们最后调用IApplicationBuilder接口的Run扩展方法注册了一个中间件,该中间件在处理请求时会利用依赖注入容器提取出用于发送日志事件的ILogger<Program>对象,并利用它写入一条Information等级的日志。如果请求路径为“/error”,那么该中间件会抛出一个InvalidOperationException类型的异常。

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddSimpleConsole(options => options.IncludeScopes = true);
var app = builder.Build();
app.Run(HandleAsync);
app.Run();static Task HandleAsync(HttpContext httpContext)
{var logger = httpContext.RequestServices.GetRequiredService<ILogger<Program>>();logger.LogInformation($"Log for event Foobar");if (httpContext.Request.Path == new PathString("/error")){throw new InvalidOperationException("Manually throw exception.");}return Task.CompletedTask;
}

在启动程序之后,我们利用浏览器采用不同的路径(“/foobar”和“/error”)向应用发送了两次请求,控制台上会输出如图1所示的七条日志。由于开启了日志范围的支持,所以输出的日志都会携带日志范围的信息,日志范围提供了很多有用的分布式跟踪信息,比如Trace ID、Span ID、Parent Span ID以及请求的ID和路径等。请求ID(Request ID),它由当前的连接ID和一个序列号组成。从图1可以看出,两次请求的ID分别是“0HMG97FD188VR:00000002”和“0HMG97FD188VR:00000003”。由于采用的是长连接,并且两次请求共享同一个连接,所以它们具有相同的连接ID(“0HMG97FD188VR”)。同一连接的多次请求将一个自增的序列号(“00000002”和“00000003”)作为唯一标识。

86117a6195dbea6cd9d2506458ea62b6.png
图1 捕捉HostingApplication记录的诊断日志

对于两次请求输出的七条日志,类别为“Program”的日志是应用程序自行写入的,HostingApplication写入日志的类别为“Microsoft.AspNetCore.Hosting.Diagnostics”。对于第一次请求的三条日志消息,第一条是在开始处理请求时写入的,我们利用这条日志获知请求的HTTP版本(HTTP/1.1)、HTTP方法(GET)和请求URL。对于包含主体内容的请求,请求主体内容的媒体类型(Content-Type)和大小(Content-Length)也会一并记录下来。当请求处理结束后第三条日志被输出,日志承载的信息包括请求处理耗时(9.9482毫秒)和响应状态码(200)。如果响应具有主体内容,对应的媒体类型同样会被记录下来。

对于第二次请求,由于我们人为抛出了异常,所以异常的信息被写入日志。如果足够仔细,就会发现这条等级为Error的日志并不是由HostingApplication对象写入的,而是作为服务器的KestrelServer写入的,因为该日志采用的类别为“Microsoft.AspNetCore.Server.Kestrel”。

[S1702]收集DiagnosticSource输出的日志

HostingApplication采用的三种日志形式还包括基于DiagnosticSource对象的诊断日志,所以我们可以通过注册诊断监听器来收集诊断信息。如果通过这种方式获取诊断信息,就需要预先知道诊断日志事件的名称和内容荷载的数据结构。通过查看HostingApplication类型的源代码,我们会发现它针对“开始请求”、“结束请求”和“未处理异常”这三类诊断日志事件会采用如下的命名方式。

  • 开始请求:Microsoft.AspNetCore.Hosting.BeginRequest。

  • 结束请求:Microsoft.AspNetCore.Hosting.EndRequest。

  • 未处理异常:Microsoft.AspNetCore.Hosting.UnhandledException。

至于针对诊断日志消息的内容荷载(Payload)的结构,上述三类诊断事件具有两个相同的成员,分别是表示当前请求上下文的HttpContext和通过一个Int64整数表示的当前时间戳,对应的数据成员的名称分别为“httpContext”和“timestamp”。对于未处理异常诊断事件,它承载的内容荷载还包括抛出异常,对应的成员名称为“exception”。我们的演示程序定义了如下这个的DiagnosticCollector类型作为诊断监听器,它定义针对上述三个诊断事件的监听方法。

public class DiagnosticCollector
{[DiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]public void OnRequestStart(HttpContext httpContext, long timestamp){var request = httpContext.Request;Console.WriteLine($"\nRequest starting {request.Protocol} {request.Method} {request.Scheme}://{request.Host}{request.PathBase}{request.Path}");httpContext.Items["StartTimestamp"] = timestamp;}[DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]public void OnRequestEnd(HttpContext httpContext, long timestamp){var startTimestamp = long.Parse(httpContext.Items["StartTimestamp"]!.ToString());var timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;var elapsed = new TimeSpan((long)(timestampToTicks * (timestamp - startTimestamp)));Console.WriteLine($"Request finished in {elapsed.TotalMilliseconds}ms {httpContext.Response.StatusCode}");}[DiagnosticName("Microsoft.AspNetCore.Hosting.UnhandledException")]public void OnException(HttpContext httpContext, long timestamp, Exception exception){OnRequestEnd(httpContext, timestamp);Console.WriteLine($"{exception.Message}\nType:{exception.GetType()}\nStacktrace: {exception.StackTrace}");}
}

针对“开始请求”事件的OnRequestStart方法输出了当前请求的HTTP版本、HTTP方法和URL。为了能够计算整个请求处理的耗时,它将当前时间戳保存在HttpContext上下文的Items集合中。针对“结束请求”事件的OnRequestEnd方法将这个时间戳从HttpContext上下文中提取出来,结合当前时间戳计算出请求处理耗时,该耗时和响应的状态码最终会被写入控制台。针对“未处理异常”诊断事件的OnException方法则在调用OnRequestEnd方法之后将异常的消息、类型和跟踪堆栈输出到控制台上。如下所示的演示程序中利用WebApplication的Services提供的依赖注入容器提取出注册的DiagnosticListener对象,并调用它的SubscribeWithAdapter扩展方法将DiagnosticCollector对象注册为订阅者。我们调用Run扩展方法注册了一个中间件,该中间件会在请求路径为“/error”的情况下抛出异常。

using App;
using System.Diagnostics;var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
var app = builder.Build();
var listener = app.Services.GetRequiredService<DiagnosticListener>();
listener.SubscribeWithAdapter(new DiagnosticCollector());
app.Run(HandleAsync);
app.Run();static Task HandleAsync(HttpContext httpContext)
{var listener = httpContext.RequestServices.GetRequiredService<DiagnosticListener>();if (httpContext.Request.Path == new PathString("/error")){throw new InvalidOperationException("Manually throw exception.");}return Task.CompletedTask;
}

待演示实例正常启动后,可以采用不同的路径(“/foobar”和“/error”)对应用程序发送两个请求,服务端控制台会以图2所示的形式输出DiagnosticCollector对象收集的诊断信息。

01a984c86d0cf32284f0d0af7fe23773.png
图2 利用注册的诊断监听器获取诊断日志

[S1703]收集EventSource输出的日志

HostingApplication在处理每个请求的过程中还会利用名称为“Microsoft.AspNetCore.Hosting”EventSource对象发出相应的日志事件。这个EventSource对象来回在在启动和关闭应用程序时发出相应的事件。涉及的五个日志事件对应的名称如下:

  • 启动应用程序:HostStart。

  • 开始处理请求:RequestStart。

  • 请求处理结束:RequestStop。

  • 未处理异常:UnhandledException。

  • 关闭应用程序:HostStop。

如下所示的演示程序利用创建的EventListener对象来监听上述五个日志事件。如代码片段所示,我们定义了派生于抽象类EventListener的DiagnosticCollector类型,并在启动应用前创建了这个对象,我们通过注册它的EventSourceCreated事件开启了针对上述EventSource的监听。注册的EventWritten事件会将监听到的事件名称的负载内容输出到控制台上。

using System.Diagnostics.Tracing;var listener = new DiagnosticCollector();
listener.EventSourceCreated += (sender, args) =>
{if (args.EventSource?.Name == "Microsoft.AspNetCore.Hosting"){listener.EnableEvents(args.EventSource, EventLevel.LogAlways);}
};
listener.EventWritten += (sender, args) =>
{Console.WriteLine(args.EventName);for (int index = 0; index < args.PayloadNames?.Count; index++){Console.WriteLine($"\t{args.PayloadNames[index]} = {args.Payload?[index]}");}
};var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
var app = builder.Build();
app.Run(HandleAsync);
app.Run();static Task HandleAsync(HttpContext httpContext)
{if (httpContext.Request.Path == new PathString("/error")){throw new InvalidOperationException("Manually throw exception.");}return Task.CompletedTask;
}public class DiagnosticCollector : EventListener { }

以命令行的形式启动这个演示程序后,从图3所示的输出结果可以看到名为HostStart的事件被发出。然后我们采用目标地址“http://localhost:5000/foobar”和“http:// http://localhost:5000/error”对应用程序发送两个请求,从输出结果可以看出,应用程序针对前者的处理过程会发出RequestStart事件和RequestStop事件,针对后者的处理则会因为抛出的异常发出额外的事件UnhandledException。输入“Ctrl+C”关闭应用后,名称为HostStop的事件被发出。对于通过EventSource发出的五个事件,只有RequestStart事件会将请求的HTTP方法(GET)和路径(“/foobar”和“/error”)作为负载内容,其他事件都不会携带任何负载内容。

f74988b8ddebc9b07af305e2d0dd5d6d.png
图3 利用注册EventListener监听器获取诊断日志

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

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

相关文章

C语言循环为1404的循环,考试,求大神帮忙,C语言,小弟感激不尽

若有定义语句&#xff1a;int a10; double b3.14;&#xff0c;则表达式Aab值的类型是___________。  (1)A).char B)int C) double D)float(2)若有定义语句&#xff1a;int x12,y8,z;&#xff0c;在其后执行语句z0.9x/y;&#xff0c;则z的值为___________。A)1.9 B)1 C)2 D)2.…

js题集19

1.实现斐波那契数列。达到题目中的效果。不知道斐波那契数列是啥的请自行百度。 function fibonacci(){ } var ffibonacci(); for(var i0;i<10;i){ console.log(f()); } //output:按顺序输出斐波那契数列的数字。 eg&#xff1a; 1 2 3 5 8 13 21 34 55 89转载于:https://ww…

阿里云Maven镜像配置

2019独角兽企业重金招聘Python工程师标准>>> <mirror><id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> …

c语言中有12个球,数学老师做不出来的一道逻辑推理题

同志们 那个球不一定轻啊正确的是平分三份 取两分称if(平)。。。。。。在未称过的4球中取两个放左边 和标准的球称(称过的球一定标准)。。。。。。if(平)。。。。。。。。。。。。在两次都未称过的球中取一个 和标准的称。。。。。。。。。。。。if(平)。。。。。。。。。。。。…

WPF 实现弹幕效果

WPF 实现弹幕效果控件名&#xff1a;BarrageExample作者&#xff1a;WPFDevelopersOrg原文链接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用大于等于.NET40&#xff1b;Visual Studio 2022;项目使用 MIT 开源许可协议&#xff1b;此篇代码目的只…

js题集23

1.实现函数--defaultArguments 功能如下&#xff1a; function add(a,b) { return ab;}; var add_ defaultArguments(add,{b:9}); add_(10); // returns 19 add_(10,7); // returns 17 add_(); // returns NaN add_ defaultArguments(add_,{b:3, a:2}); add_(10); // returns…

iteritems()与items()

iteritems&#xff1a;以迭代器对象返回字典键值对 item:以列表形式返回字典键值对 >>> dic {a:3,c:1,b:2} >>> print dic.iteritems() <dictionary-itemiterator object at 0x7fa381599628> >>> print dic.items() [(a, 3), (c, 1), (b, 2)…

WPF效果第一百九十八篇之模块对比

前面效果中分享了彩色马蹄图的效果和范围内拖拽;这不大假期的时间反正没啥事就在家撸代码;今天又是LisBox实现的效果,看最终效果:1、刚开始一朋友说用DataGrid来实现.首先把行对象转换成列对象,至于控制列的话,就后台重新赋值对象来控制前台.我是觉得太费劲直接放弃了;还是首选…

android 与后台通信,Android后台线程和UI线程通讯实例

本节向你展示如何在任务中发送数据给UI线程里的对象&#xff0c;这个特性允许你在后台线程工作&#xff0c;完了在UI线程展示结果。在UI线程定义一个HandlerHandler是Android系统线程管理框架里的一部分。一个Handler对象接收消息&#xff0c;并且运行代码来处理消息。正常情况…

saltstack的状态文件

saltstack状态文件设定&#xff1a;编辑/etc/salt/master&#xff0c;修改其中关于“设置文件的目录”的设置&#xff1a;说明&#xff1a;注意语法格式&#xff0c;顶格/冒号/两个空格state_top: top.sls # The state system uses a "top" file to tell the minions…

POJ 2798:二进制转换十六进制

很郁闷&#xff0c;这道题一直WA&#xff0c;然而本地我测了好几组数据都是通过的&#xff0c;上网找了网友陈宇龙加油加油加油的AC的代码&#xff0c;http://blog.csdn.net/Since_natural_ran/article/details/51742149&#xff0c;发现没有什么不同。。。很无语。。 #include…

【Shashlik.EventBus】.NET 事件总线,分布式事务最终一致性简介

分布式事务、CAP定理、事件总线&#xff0c;在当前微服务、分布式、集群大行其道的架构前提下&#xff0c;是不可逃避的几个关键字&#xff0c;在此不会过多阐述相关的理论知识。Shashlik.EventBus就是一个基于.NET6的开源事件总线解决方案&#xff0c;同时也是分布式事务最终一…

5个超实用的Visual Studio插件

工欲善其事&#xff0c;必先利其器,整理的一些我必装的5款Visual Studio插件&#xff0c;希望你们能get到。01 CodeMaidCodeMaid快速整理代码文件&#xff0c;规范你的代码&#xff0c;提高代码阅读体验。代码自动对齐&#xff0c;格式化代码&#xff08;ps&#xff1a;不用再按…

BZOJ1509: [NOI2003]逃学的小孩(树的直径)

Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1126 Solved: 567[Submit][Status][Discuss]Description Input 第一行是两个整数N&#xff08;3  N  200000&#xff09;和M&#xff0c;分别表示居住点总数和街道总数。以下M行&#xff0c;每行给出一条街道的信息。第i1行…

Blazor University (52)依赖注入 —— 拥有多个依赖项:正确的方式

原文链接&#xff1a;https://blazor-university.com/dependency-injection/component-scoped-dependencies/owning-multiple-dependencies-the-right-way/拥有多个依赖项&#xff1a;正确的方式在上一节[1]中&#xff0c;我们看到了将多个拥有的依赖项注入组件的错误方法。本节…

Gradle 1.12用户指南翻译——第五十四章. 构建原生二进制文件

其他章节的翻译请参见&#xff1a;http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上的地址&#xff1a;https://github.com/msdx/gradledoc本文翻译所在分支&#xff1a;https://github.com/msdx/gradledoc/tree/1.12。直接浏览双语版的文档…

android 调用c wcf服务,如何使用命名管道从c调用WCF方法?

更新&#xff1a;通过协议here,我无法弄清楚未知的信封记录.我在网上找不到任何例子.原版的&#xff1a;我有以下WCF服务static void Main(string[] args){var inst new PlusFiver();using (ServiceHost host new ServiceHost(inst,new Uri[] { new Uri("net.pipe://loc…

VK Cup 2015 - Qualification Round 1 A. Reposts(树)

传送门 Description One day Polycarp published a funny picture in a social network making a poll about the color of his handle. Many of his friends started reposting Polycarps joke to their news feed. Some of them reposted the reposts and so on. These event…

Lombok@Builder和@NoArgsConstructor冲突

问题 今天在使用lombok简化model类时。使用Builder建造者模式。报以下异常 解决办法。 去掉NoArgsConstructor添加AllArgsConstructor源码分析 下图是编译后的源码 只使用Builder会自动创建全参构造器。而添加上NoArgsConstructor后就不会自动产生全参构造器

现在商业有种竞争叫“跨界打击”

随着互联网的发展&#xff0c;“跨界打击”的事情可谓是无处不在。行业跨界打击会抢占某个行业的市场份额&#xff0c;甚至可能淘汰一个行业。跨界打击者可能是某个行业的新进入者&#xff0c;也可能是现有竞争者&#xff0c;更可能是彻底的替代者或颠覆者。跨界打击&#xff0…