查看原文
其他

ASP.NET Core 利用文件监视进行快速测试开发

DotNet 2019-08-03

(给DotNet加星标,提升.Net技能


转自:Ron.liang

cnblogs.com/viter/p/10128637.html


前言


在.NET Core 2.2 中,官方文档表示,对 EventListener 这个日志监视类的内容进行了扩充,同时赋予了跟踪 CoreCLR 事件的权限;通过跟踪 CoreCLR 事件,比如通过跟踪 CoreCLR 事件,可以了解和收集到比如 GC,JIT,ThreadPool,intreop 这些运行时服务的行为;通过使用配置注入,我们将获得一种动态跟踪事件的能力。


一、EventListener 介绍


1.1、EventListener 中文直译为:事件侦听器


EventListener 位于程序集 System.Diagnostics.Tracing 中,该类提供了一组启用/禁用的方法,按照惯例,先来看一下源代码,了解一下其结构


public abstract class EventListener : IDisposable
{
   protected EventListener();
   public event EventHandler<EventSourceCreatedEventArgs> EventSourceCreated;
   public event EventHandler<EventWrittenEventArgs> EventWritten;
   protected static int EventSourceIndex(EventSource eventSource);
   public void DisableEvents(EventSource eventSource);
   public virtual void Dispose();
   public void EnableEvents(EventSource eventSource, EventLevel level);
   public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword);
   protected internal virtual void OnEventWritten(EventWrittenEventArgs eventData);
}


从类结构中可以看出,EventListener 中的方法并不多,而且从名字都可以推断出其行为,因为该类是一个抽象类,并不能直接使用,接下来我们创建一个 ReportListener 类继承它


二、创建自定义事件侦听器


public class ReportListener : EventListener
{
   public ReportListener() { }
   public Dictionary<string, ListenerItem> Items { get; set; } = new Dictionary<string, ListenerItem>();
   public ReportListener(Dictionary<string, ListenerItem> items)
   
{
       this.Items = items;
   }
   protected override void OnEventSourceCreated(EventSource eventSource)
   
{
       if (Items.ContainsKey(eventSource.Name))
       {
           var item = Items[eventSource.Name];
           EnableEvents(eventSource, item.Level, item.Keywords);
       }
   }
   protected override void OnEventWritten(EventWrittenEventArgs eventData)
   
{
       if (Items.ContainsKey(eventData.EventSource.Name))
       {
           Console.WriteLine($"ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventSource.Name}.{eventData.EventName}");
           for (int i = 0; i < eventData.Payload.Count; i++)
           {
               string payloadString = eventData.Payload[i]?.ToString() ?? string.Empty;
               Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
           }
           Console.WriteLine("\n");
       }
   }
}


ReportListener 自定义事件侦听器的代码非常简单,只是简单的继承了 EventListener 后,重写了父类的两个方法:创建事件和写入事件

同时,还定义了一个公共属性 Dictionary<string, ListenerItem> Items ,该属性接受一个 ListenerItem 的跟踪配置集,通过配置文件注入,动态觉得哪些事件可以被写入到侦听器中


三、配置跟踪项目


在配置文件 appsettings.json 中增加以下内容


{
 "listener": [
   {
     "name": "HomeEventSource",
     "level": 5,
     "keywords": -1
   }
 ]
}


配置说明


上面的配置文件表示,定义一个事件源对象(EventSource)


名称为 HomeEventSource,事件级别(EventLevel)为 5,


关键字(EventKeywords)为 -1


关于事件级别和事件关键字的值,和系统定义的一致


3.1、事件级别定义


namespace System.Diagnostics.Tracing
{
   public enum EventLevel
   {
       LogAlways = 0,
       Critical = 1,
       Error = 2,
       Warning = 3,
       Informational = 4,
       Verbose = 5
   }
}


3.2、事件关键字定义


namespace System.Diagnostics.Tracing
{
   [Flags]
   public enum EventKeywords : long
   {
       All = -1,
       None = 0,
       WdiContext = 562949953421312,
       MicrosoftTelemetry = 562949953421312,
       WdiDiagnostic = 1125899906842624,
       Sqm = 2251799813685248,
       AuditFailure = 4503599627370496,
       CorrelationHint = 4503599627370496,
       AuditSuccess = 9007199254740992,
       EventLogClassic = 36028797018963968
   }
}


3.3、配置文件完全按照系统值定义,为了更好的使用配置文件,我们定义了下面的实体类


public class ListenerItem
{
   public string Name { get; set; }
   public EventLevel Level { get; set; } = EventLevel.Verbose;
   public EventKeywords Keywords { get; set; } = EventKeywords.All;
}


四、开始使用事件侦听器


为了在应用程序中使用事件侦听器,我们需要初始化事件侦听器,你可以初始化多个事件侦听器;但是,每个事件侦听器仅需要初始化一次即可


4.1、初始化自定义事件侦听器,在 Startup.cs 文件中加入以下代码


public void AddEventListener(IServiceCollection services)
{
   var listeners = this.Configuration.GetSection("listener").Get<List<ListenerItem>>();
   Dictionary<string, ListenerItem> dict = new Dictionary<string, ListenerItem>();
   if (listeners != null)
   {
       foreach (var item in listeners)
       {
           dict.Add(item.Name, item);
       }
   }
   var report = new ReportListener(dict);
   services.AddSingleton<ReportListener>(report);
}
public void ConfigureServices(IServiceCollection services)
{
   AddEventListener(services);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}


初始化动作非常简单,仅是从配置文件中读取需要跟踪的项,然后注册到 ReportListener 内部即可,为了演示事件的注册,我们需要创建一个事件源,就像配置文件中的名称 HomeEventSource


4.2、创建自定义的事件源对象


public class HomeEventSource : EventSource
{
   public static HomeEventSource Instance = new HomeEventSource();
   [Event(1001)]
   public void RequestStart(string message) => WriteEvent(1001, message);
   [Event(1002)]
   public void RequestStop(string message) => WriteEvent(1002, message);
}


自定义事件源 HomeEventSource 继承自 EventSource,我们可无需为该自定义事件源进行显式命名,因为默认将会使用 HomeEventSource 类名进行注册事件

现在,我们尝试着 HomeController 去生产一个事件,看看效果


五、生产事件


5.1、转到 HomeController,在 HomeController 的 Get 方法中使用 HomeEventSource 生产两个事件


[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
   [HttpGet]
   public ActionResult<IEnumerable<string>> Get()
   {
       HomeEventSource.Instance.RequestStart("处理业务开始");
       var arra = new string[] { "value1", "value2" };
       HomeEventSource.Instance.RequestStop("处理业务结束");
       return arra;
   }
}


5.2、回顾一下自定义事件侦听器 ReportListener 的重写方法


protected override void OnEventSourceCreated(EventSource eventSource)
{
   if (Items.ContainsKey(eventSource.Name))
   {
       var item = Items[eventSource.Name];
       EnableEvents(eventSource, item.Level, item.Keywords);
   }
}

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
   if (Items.ContainsKey(eventData.EventSource.Name))
   {
       Console.WriteLine($"ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventSource.Name}.{eventData.EventName}");
       for (int i = 0; i < eventData.Payload.Count; i++)
       {
           string payloadString = eventData.Payload[i]?.ToString() ?? string.Empty;
           Console.WriteLine($"\tName = \"{eventData.PayloadNames[i]}\" Value = \"{payloadString}\"");
       }
       Console.WriteLine("\n");
   }
}


由于我们做配置文件中指定了必须是 HomeEventSource 事件源才启用事件,所以上面的代码表示,当一个 HomeEventSource 事件进入的时候,将事件的内容打印到控制台,实际应用中,你可以将这些信息推送到日志订阅服务器,以方便跟踪和汇总


5.3、运行程序,看看输出结果如何



可以看到,事件生产成功,实际上,CoreCLR 内部生产了非常多的事件,下面我们尝试启用以下 3 个事件源,预期将会收到大量的事件信息


5.4、尝试更多事件源


protected override void OnEventSourceCreated(EventSource eventSource)
{
   if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
   {
       EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.AuditFailure);
   }
   else if (eventSource.Name.Equals("System.Data.DataCommonEventSource"))
   
{
       EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.AuditFailure);
   }
   else if (eventSource.Name.Equals("Microsoft-AspNetCore-Server-Kestrel"))
   
{
       EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.AuditFailure);
   }
}


5.5、再次运行程序,看下图输出结果



从图中可以看出,这次我们跟踪到了 Microsoft-AspNetCore-Server-Kestrel 事件源生产的开始和结束连接事件


结束语


  • 在 CoreCLR 的事件总线中,包含了千千万万的事件源生产的事件,以上的实验只是冰山一角,如果你把创建事件源的 EventKeywords 指定为 All,你将会看到天量的日志信息,但是,在这里,友情提示大家,千万不要这样做,这种做法会对服务性能带来极大损害。


  • 在业务代码中,写入大量的调试日志是不可取的,但是使用事件侦听器,可以控制事件的创建和写入,当需要对某个接口进行监控的时候,通过将需要调试的事件源加入配置文件中进行监控,这将非常有用。


代码下载:https://files.cnblogs.com/files/viter/Ron.ListenerDemo.zip


推荐阅读

(点击标题可跳转阅读)

ASP.NET Core开发指南

ASP.NET Core 被低估的过滤器

ASP.NET Core 一行代码搞定文件上传


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存