前言
Windows 事件日誌 記錄系統、應用程式與安全性相關事件,是系統監控、故障診斷與安全稽核的關鍵資料來源。.NET Framework 提供 System.Diagnostics.EventLog 與 EventLogEntry 類別,讓 C# 開發者能輕松讀取、篩選、監控事件日誌。本文詳細解析核心類別、屬性、方法與實務範例。
核心類別架構
EventLog 類別 (System.Diagnostics)
代表單一事件日誌 (如 Application、System、Security),提供讀寫、監控功能。
| 屬性/方法 | 說明 |
| Log | 取得/設定日誌名稱 (Application、System、Security、自訂日誌) |
| Source | 取得/設定事件來源名稱 (寫入時需註冊) |
| MachineName | 取得/設定目標電腦名稱 (預設為本機 ".") |
| Entries | 取得 EventLogEntryCollection,包含所有日誌項目 |
| WriteEntry() | 寫入事件日誌 |
| Clear() | 清除日誌 (需管理員權限) |
| DeleteEventSource() | 刪除事件來源註冊 |
EventLogEntry 類別
代表單一事件日誌記錄,唯讀物件,包含豐富的事件中繼資料。
| 屬性 | 型別 | 說明 |
| EntryType | EventLogEntryType | 事件類型:Error、Warning、Information、SuccessAudit、FailureAudit |
| EventID | int | 應用程式定義的事件識別碼 |
| Source | string | 產生事件的來源名稱 |
| Message | string | 完整的本地化事件訊息 |
| TimeGenerated | DateTime | 事件產生的本地時間 |
| TimeWritten | DateTime | 事件寫入日誌的本地時間 |
| MachineName | string | 產生事件的電腦名稱 |
| UserName | string | 相關使用者帳號 (可能為空) |
| Category | string | 事件類別文字 |
| Data | byte[] | 事件相關的二進位資料 |
EventLogEntryType 列舉
| 值 | 說明 | 典型用途 |
| Error | 錯誤事件,嚴重問題 (功能失效、資料遺失) | 應用程式崩潰、磁碟故障 |
| Warning | 警告事件,潛在問題 | 磁碟空間不足、連線逾時 |
| Information | 資訊事件,成功操作 | 服務啟動、使用者登入 |
| SuccessAudit | 成功稽核,安全存取成功 | 成功登入、檔案存取 |
| FailureAudit | 失敗稽核,安全存取失敗 | 登入失敗、權限不足 |
實務範例 1:讀取系統日誌並篩選錯誤事件
using System;
using System.Diagnostics;
using System.Linq;
public class EventLogReader
{
///
/// 讀取指定日誌近期的錯誤事件
///
public static void ReadRecentErrors(string logName = "System", int maxCount = 50)
{
using (EventLog eventLog = new EventLog(logName))
{
// 由新到舊排序 (Entries 索引 0 為最舊)
var entries = eventLog.Entries.Cast<EventLogEntry>()
.Where(e => e.EntryType == EventLogEntryType.Error)
.OrderByDescending(e => e.TimeGenerated)
.Take(maxCount);
Console.WriteLine($"=== {logName} 日誌 - 最近 {maxCount} 筆錯誤事件 ===");
foreach (var entry in entries)
{
Console.WriteLine($"時間: {entry.TimeGenerated:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"來源: {entry.Source}");
Console.WriteLine($"事件 ID: {entry.EventID}");
Console.WriteLine($"訊息: {entry.Message}");
Console.WriteLine(new string('-', 60));
}
}
}
///
/// 統計各類型事件數量
///
public static void StatisticEventTypes(string logName = "System")
{
using (EventLog eventLog = new EventLog(logName))
{
var stats = eventLog.Entries.Cast<EventLogEntry>()
.GroupBy(e => e.EntryType)
.Select(g => new { Type = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
Console.WriteLine($"=== {logName} 日誌事件類型統計 ===");
foreach (var stat in stats)
{
Console.WriteLine($"{stat.Type,-15} : {stat.Count,6} 筆");
}
}
}
}
實務範例 2:即時監控日誌新增事件 (EventLogWatcher 模式)
using System;
using System.Diagnostics;
public class EventLogMonitor
{
private EventLog _eventLog;
private bool _isMonitoring;
public void StartMonitoring(string logName = "Application", string sourceFilter = null)
{
_eventLog = new EventLog(logName);
_isMonitoring = true;
// 記錄目前最新索引,避免重複讀取舊事件
int lastIndex = _eventLog.Entries.Count - 1;
Task.Run(() =>
{
while (_isMonitoring)
{
try
{
int currentCount = _eventLog.Entries.Count;
for (int i = lastIndex + 1; i < currentCount; i++)
{
var entry = _eventLog.Entries[i];
if (sourceFilter == null || entry.Source == sourceFilter)
{
OnNewEntry(entry);
}
}
lastIndex = currentCount - 1;
}
catch (Exception ex)
{
Console.WriteLine("監控錯誤: " + ex.Message);
}
Thread.Sleep(1000); // 每秒輪詢一次
}
});
}
public void StopMonitoring() => _isMonitoring = false;
protected virtual void OnNewEntry(EventLogEntry entry)
{
Console.WriteLine($"[{entry.TimeGenerated:HH:mm:ss}] {entry.EntryType} - {entry.Source}: {entry.Message}");
}
}
實務範例 3:寫入自訂應用程式日誌
public class ApplicationLogger
{
private const string SourceName = "MyApp";
private const string LogName = "Application";
public ApplicationLogger()
{
// 首次執行需註冊來源 (需管理員權限)
if (!EventLog.SourceExists(SourceName))
{
EventLog.CreateEventSource(SourceName, LogName);
}
}
public void LogInfo(string message, int eventId = 1000)
{
EventLog.WriteEntry(SourceName, message, EventLogEntryType.Information, eventId);
}
public void LogError(string message, Exception ex = null, int eventId = 1001)
{
string fullMessage = ex != null ? $"{message}\n{ex}" : message;
EventLog.WriteEntry(SourceName, fullMessage, EventLogEntryType.Error, eventId);
}
public void LogWarning(string message, int eventId = 1002)
{
EventLog.WriteEntry(SourceName, message, EventLogEntryType.Warning, eventId);
}
}
遠端電腦日誌讀取
// 讀取遠端電腦的系統日誌 (需具備遠端電腦管理權限)
EventLog remoteLog = new EventLog("System", "192.168.1.100", "System");
// 或指定認證 (需搭配 Impersonation 或 WMI)
效能與最佳實務建議
- 使用 Cast<EventLogEntry>() + LINQ:
Entries 是非泛型集合,搭配 LINQ 篩選極為方便
- 避免一次載入所有項目:大型日誌可能有數十萬筆,使用
Take()、Skip() 分頁讀取
- 快取最後讀取索引:輪詢模式下記錄
Entries.Count 避免重複處理
- 非同步處理:UI 應用請使用
Task.Run 避免阻塞主執行緒
- 權限管理:讀取 Security 日誌需管理員權限;寫入需註冊 Source (安裝時以管理員身分執行一次)
- 釋放資源:
EventLog 實作 IDisposable,請搭配 using 區塊
常見問題排查
| 問題 | 原因 | 解決 |
| System.Security.SecurityException | 權限不足 | 以管理員身分執行、或授予事件日誌讀取權限 |
| System.ArgumentException: 來源不存在 | 寫入前未註冊 Source | 呼叫 EventLog.CreateEventSource() 註冊 (需管理員) |
| 遠端讀取失敗 | RPC/防火牆/權限 | 開放 TCP 135/445、確認遠端登入權限、使用同網域帳號 |
| 中文訊息顯示亂碼 | 編碼問題 | .NET 通常自動處理,若仍有問題請檢查系統地區設定 |
總結
EventLog 與 EventLogEntry 是 .NET 存取 Windows 事件日誌的標準介面。掌握其核心屬性、篩選技巧與監控模式,能幫助開發者建立系統監控工具、診斷輔助程式、安全稽核系統等實務應用。搭配 LINQ 與非同步模式,可高效處理大量日誌資料。
- 上一篇文章: C# 取得主機板序號:使用 WMI 查詢 Win32_BaseBoard 完整範例
- 下一篇文章: C# 空值判斷完全指南:null、String.Empty、DBNull 與可為 Null 型別深度解析
Copyright ©
程式碼之家 All Rights Reserved