Asp.Net Core 輕鬆學-利用日誌監視進行服務遙測

前言

    在 Net Core 2.2 中,官方文檔表示,對 EventListener 這個日誌監視類的內容進行了擴充,同時賦予了跟蹤 CoreCLR 事件的權限;經過跟蹤 CoreCLR 事件,好比經過跟蹤 CoreCLR 事件,能夠了解和收集到好比 GC,JIT,ThreadPool,intreop 這些運行時服務的行爲;經過使用配置注入,咱們將得到一種動態跟蹤事件的能力。git

1. EventListener 介紹

1.1 EventListener 中文直譯爲:事件偵聽器

EventListener 位於程序集 System.Diagnostics.Tracing 中,該類提供了一組啓用/禁用的方法,按照慣例,先來看一下源代碼,瞭解一下其結構github

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 類繼承它json

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 的跟蹤配置集,經過配置文件注入,動態以爲哪些事件能夠被寫入到偵聽器中api

3. 配置跟蹤項目

在配置文件 appsettings.json 中增長如下內容服務器

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

配置說明
上面的配置文件表示,定義一個事件源對象(EventSource),名稱爲 HomeEventSource,事件級別(EventLevel)爲 5,關鍵字(EventKeywords)爲 -1
關於事件級別和事件關鍵字的值,和系統定義的一致app

3.1 事件級別定義ide

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 配置文件徹底按照系統值定義,爲了更好的使用配置文件,咱們定義了下面的實體類this

public class ListenerItem
    {
        public string Name { get; set; }
        public EventLevel Level { get; set; } = EventLevel.Verbose;
        public EventKeywords Keywords { get; set; } = EventKeywords.All;
    }
4. 開始使用事件偵聽器

爲了在應用程序中使用事件偵聽器,咱們須要初始化事件偵聽器,你能夠初始化多個事件偵聽器;可是,每一個事件偵聽器僅須要初始化一次便可spa

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://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.ListenerDemo

相關文章
相關標籤/搜索