.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。
[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