はじめに
仕事でトラブル解析を行う際、イベントログを確認することがありました。
本記事では、まず、Windows イベントログのしくみ等について説明して、C# での操作について記載します。
最後に、固有のメッセージ DLL を利用する形態について記載します。
テスト環境
ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
- Windows Forms - .NET 8
- WPF - .NET Framework 4.8
- WPF - .NET 8
記載したソースコードは .NET 8 ベースとしています。
.NET Framework 4.8 の場合は、コメントで記載している null 許容参照型の明示 ? を削除してください。
Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。
Windows イベントログ
Windows イベントログは、Windows としてのログ管理機構で、システムやアプリケーションの動作、セキュリティイベントなどを記録する役割を持っています。
この情報を確認することで、システム障害や不正アクセスなどの事象を、時系列で確認できます。
ログファイル
ログファイルは、EVTX 形式(Windows XML Event Log Format)のファイルとして保存されます。
EVTX 形式は、バイナリ形式で圧縮された XML を格納しており、ファイル拡張子は .evtx で、以下のフォルダに格納されています。
C:\Windows\System32\winevt\Logs
EVTX 形式の詳細については、GitHub - libyal/libevtx/documentation/Windows XML Event Log をご確認ください。
主なログの種類
ログ名 | 説明 |
---|---|
Application | アプリケーションの動作ログ |
Security | ログオン、アクセス制御などのセキュリティ関連のログ |
System | OS やドライバのログ |
Setup | インストールやアップグレードのログ |
ForwardedEvents | 他の PC から転送されたログ |
上記以外にも、イベントログ格納フォルダには多数の EVTX形式ファイルが存在し、それぞれ異なるイベントログを記録しています。
格納情報
イベントログには、下記情報が格納されます。
項目 | 内容 |
---|---|
レベル | イベントの重要度(エラー/警告/情報など) |
日付と時刻 | イベントの発生日時 |
ソース | イベントソース |
イベントID | イベント ID |
ユーザー | イベントを発生させたユーザー |
コンピュータ | イベントを発生させたコンピュータ |
イベントデータ | イベント詳細情報 |
確認方法
イベントログは以下のツールで確認できます。
- イベントビューア(eventvwr.exe)
- GUI を持ったツールで、閲覧、検索、各種ファイル形式( .evtx / .xml / .txt / .csv )へのエクスポートなどが可能です
-
wevtutil.exe
- コマンドラインでイベントログに対する各種操作ができます
- Application ログの最新 10件をテキスト出力
wevtutil qe Application /c:10 /rd:true /f:text
- Application ログのクリア(管理者権限が必要)
wevtutil cl Application
C# 基本操作
ここでは、固有のメッセージ DLL を利用しないケースについて記載します。
サンプルとして、Application イベントログの foobarbaz イベントソースを用います。
イベントソース登録
イベントソース登録は管理者権限が必要です。
インストーラで登録する場合は、レジストリに直接登録することが一般的でしょうが、C# での手法を記載します。
string source = "foobarbaz"; // イベントソース名
string logName = "Application"; // ログの種類(Application ログ)
// イベントソース存在確認 - 管理者権限が必要
if (!EventLog.SourceExists(source))
{
// イベントソース登録 - 管理者権限が必要
EventLog.CreateEventSource(source, logName);
// 下記レジストリに登録
// HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\foobarbaz
// メッセージDLL 格納変数(EventMessageFile)を作成して既定値がセットされる
// 削除せず、そのままとする
}
イベントログ出力
private void EventLoggingDirect(int eventId, EventLogEntryType type, string message)
{
string source = "foobarbaz"; // イベントソース名
//string logName = "Application"; // ログの種類(Application ログ)
try
{
// イベントログにメッセージ出力 - Application ログは管理者権限不要
EventLog.WriteEntry(source, message, type, eventId);
}
catch (SecurityException)
{
// TODO - 対象イベントソースが存在しないため、メッセージ出力エラー
Console.WriteLine("対象イベントソースが存在しません。");
}
}
以下のように利用します。
EventLoggingDirect(10, EventLogEntryType.Information, "サンプルです");
イベントログ取得
前述、イベントソース foobarbaz について、日時が本日で最新 10件を取得するコードを記載します。
string source = "foobarbaz"; // イベントソース名
string logName = "Application"; // ログの種類(Application ログ)
DateTime today = DateTime.Today; // 本日の日付(時刻は 00:00:00)
// イベントログ取得 - Application ログは管理者権限不要
// 当日で、イベントソースが foobarbaz の最新 10件にフィルタリング
EventLog eventLog = new EventLog(logName);
var filteredEntries = eventLog.Entries.Cast<EventLogEntry>()
.Where(entry => entry.TimeGenerated.Date == today) // 先に日付フィルタを適用
.Where(entry => entry.Source == source) // 次にソースフィルタを適用
.OrderByDescending(entry => entry.TimeGenerated)
.Take(10);
foreach (var entry in filteredEntries)
{
Console.WriteLine($"イベント ID: {entry.InstanceId}");
Console.WriteLine($"レベル: {entry.EntryType}");
Console.WriteLine($"ソース: {entry.Source}");
Console.WriteLine($"日時: {entry.TimeGenerated}");
Console.WriteLine($"メッセージ: {entry.Message}");
Console.WriteLine("----------------------------");
}
// リソース解放
eventLog.Close();
メッセージ DLL
イベントソース固有のメッセージ DLL を登録することで、より柔軟なメッセージ管理が可能となります。
メッセージ DLL が登録されている場合、対象イベントソースのイベント ID に関するメッセージはメッセージ DLL から取得します。
このメカニズムを採用することで、以下のようなメリットがあります。
- 多言語対応
- メッセージ DLL に複数の言語向けのメッセージを格納することで、異なる言語環境でも適切な情報表示が可能
- メッセージの更新が容易
- アプリケーションのアップデート時にメッセージ DLL を更新することで、表示メッセージも更新可能
メッセージ DLL は、イベントログファイルとイベントソースに関係づけて、レジストリで管理されています。
例えば、Application イベントログのイベントソース Outlook の場合、以下のレジストリ項目となります。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Application\Outlook
→ EventMessageFile がメッセージ DLL です
レジストリ登録情報は、Event Sources - Win32 apps | Microsoft Learn で確認できます。
メッセージファイル
多言語対応やメッセージ更新時の柔軟性を確保するため、メッセージ DLL では、イベント ID に関するメッセージに対してプレースフォルダを利用できます。
以下に、英語と日本語のメッセージを定義し、プレースフォルダも利用したメッセージファイル(メッセージ DLL の元となるデータ)のサンプルを提示します。
MessageIdTypedef=DWORD
SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
Warning=0x2:STATUS_SEVERITY_WARNING
Error=0x3:STATUS_SEVERITY_ERROR
)
FacilityNames=(System=0x0:FACILITY_SYSTEM
Runtime=0x2:FACILITY_RUNTIME
Stubs=0x3:FACILITY_STUBS
Io=0x4:FACILITY_IO_ERROR_CODE
)
LanguageNames=(English=0x409:MSG00409)
LanguageNames=(Japanese=0x411:MSG00411)
MessageId=501
Severity=Error
Facility=Io
SymbolicName=ERROR_WORKFILECREATE
Language=English
Failed to create work file.
.
Language=Japanese
ワークファイルの作成に失敗しました。
.
MessageId=502
Severity=Informational
Facility=Runtime
SymbolicName=INFO_PLACEHOLDERS
Language=English
This is a sample event log message with placeholders: %1 and %2
.
Language=Japanese
プレースフォルダ利用イベントログのサンプル: %1 と %2
.
Severity 値一覧:
値 | 意味 | イベントログのレベル |
---|---|---|
Success | 正常終了 | 情報(Information) |
Informational | 通知・情報 | 情報(Information) |
Warning | 警告 | 警告(Warning) |
Error | エラー | エラー(Error) |
Severe | 深刻なエラー(非推奨) | 通常は Error として扱われる |
イベントログのレベルは、イベント出力時に EventLogEntryType を指定すると、そちらが優先されます。
例えば、Severity=Warning となっていても、イベントログ出力時に EventLogEntryType.Error として出力すると、レベルはエラーになります。
他項目についての詳細は メッセージ テキスト ファイル - Win32 apps | Microsoft Learn を確認してください。
ビルド
ビルド用のソリューションを作成することもできますが、メッセージファイル DLL を作成する | iPentec に記載されている手順で、メッセージ DLL をビルドします。
スタートメニューから Visual Studio 2022
- x64 Native Tools Command Prompt for VS 2022
を開きます。
HogeHoge.mc を BOM付き UTF-8 ファイルとして保存し、以下のコマンドを実行してメッセージ DLL を作成します。
mc.exe -cp utf-8 HogeHoge.mc
rc.exe HogeHoge.rc
link.exe /NOENTRY /DLL /MACHINE:X64 HogeHoge.res /OUT:HogeHoge.dll
上記、DLL 作成過程で生成された HogeHoge.h にメッセージ ID が格納されています。
HogeHoge.mc で指定した SymbolicName がメッセージ ID のキーワードです。
//
// MessageId: ERROR_WORKFILECREATE
//
// MessageText:
//
// Failed to create work file.
//
#define ERROR_WORKFILECREATE ((DWORD)0xC00401F5L)
//
// MessageId: INFO_PLACEHOLDERS
//
// MessageText:
//
// This is a sample event log message with placeholders: %1 and %2
//
#define INFO_PLACEHOLDERS ((DWORD)0x400201F6L)
イベントログ出力用に C# ソースへ転記します。
private const long ERROR_WORKFILECREATE = 0xC00401F5L;
private const long INFO_PLACEHOLDERS = 0x400201F6L;
イベントソース登録
イベントソース登録は管理者権限が必要です。
インストーラで登録する場合は、レジストリに直接登録することが一般的でしょうが、C# での手法を記載します。
string source = "HogeHoge"; // イベントソース名
string logName = "Application"; // ログの種類(Application ログ)
string dllPath = @"C:\Path\To\HogeHoge.dll"; // TODO - メッセージ DLL パス
// イベントソース存在確認 - 管理者権限が必要
if (!EventLog.SourceExists(source))
{
// イベントソース登録 - 管理者権限が必要
var data = new EventSourceCreationData(source, logName)
{
MessageResourceFile = dllPath
};
EventLog.CreateEventSource(data);
// 下記レジストリに登録
// HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\HogeHoge
}
イベントログ出力
メッセージ DLL のメッセージを利用するので、プレースフォルダ値のみを出力します。
// .NETFramework の場合、引数は「object [] values」で良い
private void EventLoggingUseMessageDll(long messageId,
EventLogEntryType type, object[]? values)
{
string source = "HogeHoge"; // イベントソース名
string logName = "Application"; // ログの種類(Application ログ)
EventLog eventLog = new EventLog(logName)
{
Source = source
};
try
{
// イベントログにメッセージ出力 - Application ログは管理者権限不要
EventInstance eventInstance = new EventInstance(messageId, 0, type);
eventLog.WriteEvent(eventInstance, values);
}
catch (SecurityException)
{
// TODO - 対象イベントソースが存在しないため、メッセージ出力エラー
Console.WriteLine("対象イベントソースが存在しません。");
}
finally
{
// リソース解放
eventLog.Close();
}
}
以下のように利用します。
// messageId は HogeHoge.h から転記した値を利用
// private const long ERROR_WORKFILECREATE = 0xC00401F5L;
// private const long INFO_PLACEHOLDERS = 0x400201F6L;
EventLoggingUseMessageDll(ERROR_WORKFILECREATE, EventLogEntryType.Error, null);
object[] values = { "fuga", "piyo" };
EventLoggingUseMessageDll(INFO_PLACEHOLDERS, EventLogEntryType.Information, values);
イベントログ取得
Application イベントログについて、日時が本日で最新 10件を取得するコードとします。
イベントログを確認して、メッセージ DLL 利用であれば、メッセージ DLL からメッセージを取得します。
string logName = "Application"; // 取得するログの種類
DateTime today = DateTime.Today; // 本日の日付(時刻は 00:00:00)
// イベントログ取得 - Application ログは管理者権限不要
// 当日の最新 10件にフィルタリング
EventLog eventLog = new EventLog(logName);
var filteredEntries = eventLog.Entries.Cast<EventLogEntry>()
.Where(entry => entry.TimeGenerated.Date == today) // 日付フィルタを適用
.OrderByDescending(entry => entry.TimeGenerated)
.Take(10);
foreach (var entry in filteredEntries)
{
Console.WriteLine($"イベント ID: {entry.InstanceId}");
Console.WriteLine($"レベル: {entry.EntryType}");
Console.WriteLine($"ソース: {entry.Source}");
Console.WriteLine($"日時: {entry.TimeGenerated}");
Console.WriteLine($"メッセージ: {entry.Message}");
Console.WriteLine("----------------------------");
}
// リソース解放
eventLog.Close();