前言
在 Net Core 2.2 中, 官方文档表示, 对 EventListener 这个日志监视类的内容进行了扩充, 同时赋予了跟踪 CoreCLR 事件的权限; 通过跟踪 CoreCLR 事件, 比如通过跟踪 CoreCLR 事件, 可以了解和收集到比如 GC,JIT,ThreadPool,intreop 这些运行时服务的行为; 通过使用配置注入, 我们将获得一种动态跟踪事件的能力.
1. 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 类继承它
2. 创建自定义事件侦听器
- 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 的跟踪配置集, 通过配置文件注入, 动态觉得哪些事件可以被写入到侦听器中
3. 配置跟踪项目
在配置文件 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. 开始使用事件侦听器
为了在应用程序中使用事件侦听器, 我们需要初始化事件侦听器, 你可以初始化多个事件侦听器; 但是, 每个事件侦听器仅需要初始化一次即可
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. 生产事件
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
来源: https://www.cnblogs.com/viter/p/10128637.html