🗃️

C# - Windows イベントログ

に公開

はじめに

仕事でトラブル解析を行う際、イベントログを確認することがありました。
本記事では、まず、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

Windows イベントログ

Windows イベントログは、Windows としてのログ管理機構で、システムやアプリケーションの動作、セキュリティイベントなどを記録する役割を持っています。
この情報を確認することで、システム障害や不正アクセスなどの事象を、時系列で確認できます。

ログファイル

ログファイルは、EVTX 形式(Windows XML Event Log Format)のファイルとして保存されます。
EVTX 形式は、バイナリ形式で圧縮された XML を格納しており、ファイル拡張子は .evtx で、以下のフォルダに格納されています。

C:\Windows\System32\winevt\Logs

主なログの種類

ログ名 説明
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# での手法を記載します。

https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.eventlog.createeventsource

string source = "foobarbaz";    // イベントソース名
string logName = "Application"; // ログの種類(Application ログ)

// イベントソース存在確認 - 管理者権限が必要
if (!EventLog.SourceExists(source))
{
  // イベントソース登録 - 管理者権限が必要
  EventLog.CreateEventSource(source, logName);
  // 下記レジストリに登録
  // HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application\foobarbaz
  // メッセージDLL 格納変数(EventMessageFile)を作成して既定値がセットされる
  // 削除せず、そのままとする
}

イベントログ出力

https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.eventlog.writeentry

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 です

メッセージファイル

多言語対応やメッセージ更新時の柔軟性を確保するため、メッセージ DLL では、イベント ID に関するメッセージに対してプレースフォルダを利用できます。
以下に、英語と日本語のメッセージを定義し、プレースフォルダも利用したメッセージファイル(メッセージ DLL の元となるデータ)のサンプルを提示します。

HogeHoge.mc
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 として扱われる

他項目についての詳細は メッセージ テキスト ファイル - 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 のキーワードです。

HogeHoge.h
//
// 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 のメッセージを利用するので、プレースフォルダ値のみを出力します。

https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.eventlog.writeevent

// .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();

出典

本記事は、2025/06/16 Qiita 投稿記事の転載です。

C# - Windows イベントログ

Discussion