2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# - Windows イベントログ

Posted at

はじめに

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

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

イベントログのレベルは、イベント出力時に 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 のキーワードです。

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?