《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,一经查实,立即删除!

相关文章

阿里云Maven镜像配置

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

WPF 实现弹幕效果

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

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

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

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…

【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行…

Lombok@Builder和@NoArgsConstructor冲突

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

ntop linux,Linux下开源监控软件Ntop的性能提升方案

摘要&#xff1a;Ntop是一款Linux下常见的开源监控软件&#xff0c;它可以监测的数据包括&#xff1a;网络流量、使用协议、系统负载、端口情况、数据包发送时间等。正常情况下它工作的时候就像一部被动声纳&#xff0c;默默的接收看来自网络的各种信息&#xff0c;通过对这些数…

性能优化8--内存泄露

一.根源&#xff1a; 内存泄露简单说就是已经没有用的资源&#xff0c;但是由于被其他资源引用着无法被GC销毁。 二.内存泄露常见场景 1.单例导致内存泄露 单例的静态特性使得它的生命周期同应用的生命周期一样长&#xff0c;如果一个对象已经没有用处了&#xff0c;但是单例还…

记一次 .NET 某打印服务 非托管内存泄漏

一&#xff1a;背景 1. 讲故事前段时间有位朋友在微信上找到我&#xff0c;说他的程序出现了内存泄漏&#xff0c;能不能帮他看一下&#xff0c;这个问题还是比较经典的&#xff0c;加上好久没上非托管方面的东西了&#xff0c;这篇就和大家分享一下&#xff0c;话不多说&#…

mysql经典的8小时问题-wait_timeout

2019独角兽企业重金招聘Python工程师标准>>> 前段时间 现网突然频繁报出 连接不上数据库&#xff0c;偶滴的妖孽&#xff0c;其他地方都是用mysql&#xff0c;也没遇到这个问题呀。 java.io.EOFExceptionat at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1913…

Chrome DevTools — Network

记录网络请求 默认情况下&#xff0c;只要DevTools在开启状态&#xff0c;DevTools会记录所有的网络请求&#xff0c;当然&#xff0c;记录都是在Network面板展示的。 停止记录网络请求 点击Stop recording network log红色图标&#xff0c;当它变为灰色时&#xff0c;表示DevT…

MySQL 查看表结构简单命令

一、简单描述表结构&#xff0c;字段类型 desc tabl_name; 显示表结构&#xff0c;字段类型&#xff0c;主键&#xff0c;是否为空等属性&#xff0c;但不显示外键。 例如&#xff1a;desc table_name 二、查询表中列的注释信息 select * from information_schema.columns wher…

简单获取任意app的URL Schemes

简单说明 最近业务需要&#xff0c;一直在查询App的scheme相关信息&#xff0c;找到一种比较可靠的方法&#xff0c;分享给大家 步骤如下&#xff1a; 在电脑上使用iTunes下载那个app下载完后&#xff0c;在itunes里点击这个app&#xff0c;选择->Show in Finder&#xff0c…

Dnslog在SQL注入中的利用

参考文献&#xff1a;www.anquanke.com/post/id/98096https://bbs.pediy.com/thread-223881.htm DNSlog在Web攻击的利用 在某些无法直接利用漏洞获得回显的情况下&#xff0c;但是目标可以发起DNS请求&#xff0c;这个时候就可以通过DNSlog把想获得的数据外带出来。 常用情况 S…

让泛型的思维扎根在脑海——深刻理解泛型

1.前言往往一些刚接触C#编程的初学者&#xff0c;对于泛型的认识就是直接跳到对泛型集合的使用上&#xff0c;虽然微软为我们提供了很多内置的泛型类型&#xff0c;但是如果我们只是片面的了解调用方式&#xff0c;这会导致我们对泛型盲目的使用。至于为什么要使用泛型&#xf…

android 系统ui修改器,分享两个效果 - Android 系统 UI 管理

SystemUIManage.gifDimming the System Bars (沉浸模式)知乎 和 Medium 中都使用到了这个效果&#xff0c;作为沉浸式阅读模式。// This example uses decor view, but you can use any visible view.View decorView getWindow().getDecorView();int uiOptions View.SYSTEM_U…

打游戏要存进度-备忘录模式

打游戏要存进度-备忘录模式 学习自 《大话设计模式》 备忘录模式漫谈 备忘录的这种设计思想是非常常见的&#xff0c;比如说围棋游戏的悔棋&#xff0c;绘图软件的撤销功能等等&#xff0c;都或多或少的使用了备忘录模式来处理对象的状态。 备忘录(Memento): 在不破坏封装性的前…

利用lay-ui结合ajax实现分页功能(不借助框架,简单易懂)

效果图: 1.创建html页面 01.html(前台文件) 2.创建index.php(后台文件) ------------------热身结束,开始正式分页之旅------------------ 3.在html页面中引入layui需要用到的css以及js,还有我们自己额外需要用到的jquery 4.在html文件中,将基本的分页栏显示出来 5.好啦,htm…