閱讀159 返回首頁    go 阿裏雲 go 技術社區[雲棲]


.NET Core的日誌[4]:將日誌寫入EventLog

麵向Windows的編程人員應該不會對Event Log感到陌生,以至於很多人提到日誌,首先想到的就是EventLog。EventLog不僅僅記錄了Windows係統自身針對各種事件的日誌,我們的應用也可以利用提供的API將日誌消息寫到EventLog中。與EventLog相關的API都定義在System.Diagnostics.EventLog這個類型中,我們不僅僅可以利用它讀取、寫入和刪除日誌,還可以使用它來創建和刪除Event Source。.NET Core的日誌模型利用EventLogLogger實現了與EventLog的集成,不過EventLogLogger使用的是一個抽象化的EventLog。本文已經同步到《ASP.NET Core框架揭秘》之中]

目錄
一、抽象化的EventLog
二、EventLogLogger
三、EventLogLoggerProvider

EventLogLogger定義在“Microsoft.Extensions.Logging.EventLog”這個NuGet包中。就目前這個版本來說,該NuGet包承載的程序集隻適用於.NET Framework應用,換句話說,EventLogLogger僅僅提供針對Windows EventLog的支持。盡管如此,日誌模型仍然通過一個接口對EventLog的相關操作進行了抽象。

ConsoleLogger采用IConsole接口對針對不同平台的控製台進行了抽象,EventLogLogger使用的抽象化EventLog通過IEventLog接口來表示。如下麵的代碼片段所示,IEventLog接口僅僅定義了一個唯一的方法WriteEntry來寫日誌,提供的參數用於指定日誌的消息文本(message)、類型(type)、事件ID(eventID)以及類別(category)。為了避免單條日誌包含過多的內容,IEventLog接口定義了一個隻讀屬性MaxMessageSize來設置日誌消息的文本允許的最大長度。

   1: public interface IEventLog
   2: {
   3:     void WriteEntry(string message, EventLogEntryType type, int eventID, short category);
   4:     int MaxMessageSize { get; }
   5: }
   6:  
   7: public enum EventLogEntryType
   8: {
   9:     Error           = 1, 
  10:     Warning         = 2,
  11:     Information     = 4,
  12:     SuccessAudit    = 8 
  13:     FailureAudit    = 16
  14: }

EventLog記錄下來的沒條日誌都具有一個通過枚舉EventLogEntryType表示的類型,這個類型相當於日誌的等級。對於定義在這個枚舉中的五種類型,Error、Warning和Information與同名的日誌等級具有相同的含義,而SuccessAudit和FailureAudit代表針對“審核(Audit)”事件的日誌,它們分別代表針對“成功事件”和“失敗事件”的審核。當EventLogLogger在利用EventLog寫入日誌的時候,會將指定的日誌等級轉化成EventLog的日誌類型,轉換規則很簡單:針對Error、Warning和Information的日誌等級轉換成同名的EventLog日誌類型,其他的等級則之間轉換成Information類型。

具有如下定義的 WindowsEventLog是對IEventLog接口的默認實現者。一個WindowsEventLog實際上就是對一個EventLog對象的封裝,後者通過DiagnosticsEventLog屬性表示。這個封裝的EventLog對象在構造函數通過指定相應參數的日誌名稱(logName)、機器名(machineName)和Event Source名稱(sourceName)來創建。在實現的WriteEntry方法中,這個EventLog的WriteEntry被直接調用來完成日誌的寫入。WindowsEventLog的MaxMessageSize屬性返回一個常量31839,日誌消息文本不能超過這個長度。

   1: public class WindowsEventLog : IEventLog
   2: {
   3:     private const int MaximumMessageSize = 31839;
   4:  
   5:     public int MaxMessageSize
   6:     {
   7:         get { return MaximumMessageSize; }
   8:     }
   9:  
  10:     public System.Diagnostics.EventLog DiagnosticsEventLog { get; }
  11:  
  12:     public WindowsEventLog(string logName, string machineName, string sourceName)
  13:     {
  14:         DiagnosticsEventLog = new System.Diagnostics.EventLog(logName, machineName, sourceName);
  15:     }
  16:  
  17:     public void WriteEntry(string message, EventLogEntryType type, int eventID, short category)   
  18:     {
  19:         DiagnosticsEventLog.WriteEntry(message, type, eventID, category);
  20:     }
  21: }

日誌模型利用EventLogLogger實現了與EventLog的整合。具體來說,一個EventLogLogger實際上是對EventLog對象的封裝,它利用後者向EventLog寫入日誌。EventLogLogger類型具有如下的定義,我們可以看到它具有兩個構造函數,除了提供Logger名稱之外,其中一個構造函數具有一個類型為EventLogSettings的參數,這個封裝的EventLog對象正式通過該參數提供的信息創建出來的。

   1: public class EventLogLogger : ILogger
   2: {
   3:     public EventLogLogger(string name);
   4:     public EventLogLogger(string name, EventLogSettings settings);
   5:  
   6:     public IDisposable BeginScope<TState>(TState state);  
   7:     public bool IsEnabled(LogLevel logLevel);
   8:     public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, 
   9:     Func<TState, Exception, string> formatter);
  10: }
  11:  
  12: public class EventLogSettings
  13: {
  14:     public IEventLog              EventLog { get; set; }
  15:  
  16:     public string                 LogName { get; set; }
  17:     public string                 MachineName { get; set; }
  18:     public string                 SourceName { get; set; } 
  19:  
  20:     public Func<string, LogLevel, bool>     Filter { get; set; }
  21: }

如上麵的代碼片段所示,我們可以利用EventLogSettings對象的EventLog屬性向EventLogLogger提供一個具體的EventLog對象。如果這個屬性為Null,EventLogLogger將會利用EventLogSettings的其他三個屬性(LogName、MatcheName和SourceName)創建一個WindowsEventLog對象。除了這四個與創建或者提供EventLog對象相關的四個屬性之外,EventLogSettings還具有另一個Func<string, LogLevel, bool>類型的屬性Filter,它自然代表日誌消息過濾器。

當EventLogLogger在利用EventLogSettings創建WindowsEventLog對象的時候,如果相關的屬性(LogName、MatcheName和SourceName)在EventLogSettings對象中未作顯式設置,它會采用預定義的默認值。具體來說這三者對應的默認值為“Application”[1]、“.”(代表本機)和“Application”。如果我們調用第一個構造函數重載,實際上內部會利用這些默認值創建一個WindowsEventLog對象。如果調用構造函數時連名稱(name參數)都沒有指定,那麼類型名稱(“EventLogLogger”)將被用來命名創建的Logger。

EventLogLogger和DebugLogger一樣也不支持日誌上下文範圍,所以它的BeginScope<TState>方法和返回的對象其實毫無意義。EventLogSettings的Filter屬性返回Func<string, LogLevel, bool>對象將被IsEnabled方法使用,如果個委托對象沒有被顯式提供,意味著這個方法總是返回True。當Log方法被調用的時候,它會采用與DebugLogger完全一致的方式來格式化最終的日誌消息文本,所以針對異常的重複描述的問題依然存在。日誌消息最終通過調用EventLog的WriteEntry方法被寫到EventLog中,但是在這之前會檢查格式化後的日誌消息文本是否超過通過MaxMessageSize屬性限製的長度,如果超過這個限製,日誌消息將會被拆分並分多次寫入EventLog。

EventLogLogger由對應的EventLogLoggerProvider來提供,下麵的代碼體現了這個類型完整的定義。我們可以調用如下所示的三個擴展方法AddEventLog來創建相應的EventLogLoggerProvider並將其注冊到指定的LoggerFactory之上,我們可以通過這個方法指定用於提供或者輔助創建EventLog的EventLogSettings對象和最低日誌等級。

   1: public class EventLogLoggerProvider : ILoggerProvider, IDisposable
   2: {
   3:     private readonly EventLogSettings _settings;
   4:     public EventLogLoggerProvider() : this(null)
   5:     {}
   6:  
   7:     public EventLogLoggerProvider(EventLogSettings settings)
   8:     {
   9:         _settings = settings;
  10:     }
  11:  
  12:     public ILogger CreateLogger(string name)
  13:     {
  14:         return new EventLogLogger(name, _settings ?? new EventLogSettings());
  15:     }
  16:  
  17:     public void Dispose()
  18:     {}
  19: }
  20:  
  21: public static class EventLoggerFactoryExtensions
  22: {
  23:     public static ILoggerFactory AddEventLog(this ILoggerFactory factory);
  24:     public static ILoggerFactory AddEventLog(this ILoggerFactory factory, EventLogSettings settings);
  25:     public static ILoggerFactory AddEventLog(this ILoggerFactory factory, LogLevel minLevel);
  26: }

我們同樣通過一個簡單的控製台應用來演示針對EventLog的日誌記錄。我們首選創建一個空的控製台應用,並在project.json中添加所需的依賴。由於針對EventLog的日誌記錄隻適用於.NET Framework應用,所以我們僅僅為應用定義了一個針對.NET Framework 4.6.1(net461)的框架。

   1: {
   2:   ...
   3:   "dependencies": {
   4:     "Microsoft.Extensions.DependencyInjection"    : "1.0.0",
   5:     "Microsoft.Extensions.Logging"                : "1.0.0",
   6:     "Microsoft.Extensions.Logging.EventLog"       : "1.0.0"
   7:   },
   8:  
   9:    "frameworks": {
  10:     "net461": {
  11:       "frameworkAssemblies": {
  12:         "System.Runtime": {
  13:           "type": "build"
  14:         }
  15:       }
  16:     }
  17:   }
  18: }

我們編寫了下麵這段程序來完成針對EventLog的日誌記錄。如下麵的代碼片段所示,我們首先為即將寫入的日誌創建了一個名為“Demo”的Event Source(它一般代表日誌被寫入的應用或者服務的名稱)。接下來我們采用依賴注入的方式創建了一個LoggerFactory對象,並調用擴展方法AddEventLog創建了一個EventLoggerProvider對象並將其注冊到LoggerFactory上。我們在調用這個AddEventLog方法時指定了一個EventLogSettings對象,並將其SourceName屬性設置為“Demo”。我們最終利用這個LoggerFactory對象創建出對應的Logger,並利用它寫入了一條等級為Error的日誌。

   1: public class Program
   2: {
   3:     public static void Main(string[] args)
   4:     {
   5:         if(!EventLog.SourceExists("Demo"))
   6:         {
   7:             EventLog.CreateEventSource("Demo", "Application");
   8:         }
   9:  
  10:         new ServiceCollection()
  11:             .AddLogging()
  12:             .BuildServiceProvider()
  13:             .GetService<ILoggerFactory>()
  14:             .AddEventLog(new EventLogSettings { SourceName = "Demo" })
  15:             .CreateLogger<Program>()
  16:             .LogError("數據庫連接失敗(數據庫:{ Database},用戶名:{ User})", "TestDb", "sa");
  17:     }
  18: }

由於上麵這段程序涉及到Event Source查看和創建,所以需要在管理員權限下才能正常運行。程序運行後查看Event Viewer,我們將會看到被寫入的這條日誌消息。如圖10所示,由於我們調用擴展方法AddEventLog時提供的EventLogSettings並沒有顯式指定EventLog名稱,所以我們的日誌默認會寫入Application這個EventLog。

10


[1] Windows默認提供了三種EventLog,分別是Application、System和Security,應用產生的日誌隻能寫到Application和System日誌中,Security日誌是隻讀的。除了這三種EventLog,我們還可以為應用創建獨立的EventLog。

.NET Core的日誌[1]:采用統一的模式記錄日誌
.NET Core的日誌[2]:將日誌寫入控製台
.NET Core的日誌[3]:將日誌寫入Debug窗口
.NET Core的日誌[4]:利用EventLog寫日誌
.NET Core的日誌[5]:利用TraceSource寫日誌

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 11:05:19

  上一篇:go  .NET Core的日誌[3]:將日誌寫入Debug窗口
  下一篇:go  .NET Core的日誌[5]:利用TraceSource寫日誌