在 Net Core 2.2 中,官方文檔表示,對 EventListener 這個日誌監視類的內容進行了擴充,同時賦予了跟蹤 CoreCLR 事件的許可權;通過跟蹤 CoreCLR 事件,比如通過跟蹤 CoreCLR 事件,可以瞭解和收集到比如 GC,JIT,ThreadPool,intreop 這... ...
前言
在 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