Windowsアプリのクラッシュ時ログ出力方法

· · Windows開発, 例外処理, ログ, WER, クラッシュダンプ, 不具合調査

一番大事な考え方

落ちるプロセスの中だけで「必ずログを残す」ことはできないと割り切る。

スタック破損やメモリ破壊、強制終了まで含めると、in-processの最終ログは本質的にベストエフォート(できる限り頑張る)でしかない。だから次の3層で考える。

  1. 通常時の時系列ログ(普段から残す)
  2. 落ちる瞬間の最終クラッシュマーカー(最小限だけ残す)
  3. OSまたは別プロセスが残す証跡(WERダンプなど)

推奨アーキテクチャ:役割分担する

フェーズ 目的 やること
通常時 時系列を残す 構造化ログ、境界イベント
クラッシュ時 最低限の証跡 最終クラッシュマーカー、WERダンプ
終了直後 異常終了を検知 別プロセスでexit code記録、再起動判断
次回起動後 重い後処理 圧縮、アップロード、ユーザー通知

最小構成(小規模ツール向け)

  • 通常ログ: ローカルの追記専用ファイル
  • 最終クラッシュマーカー: 専用の短いファイル
  • ダンプ: WER LocalDumps
  • 次回起動時: 「前回異常終了しました」の表示

強めの構成(24時間運転向け)

  • workerプロセス: 本体処理
  • launcher/watchdog: 起動監視、exit記録、再起動
  • WER LocalDumps: worker側
  • 次回起動またはwatchdog: 診断情報回収

通常ログのベストプラクティス

ログには最低限以下を入れる。

  • UTCタイムスタンプ
  • PIDとTID(プロセスIDとスレッドID)
  • アプリ名、バージョン、ビルド番号
  • セッションID
  • 操作ID(ジョブIDなど)
  • 直前の外部作用(ファイル書き込み、DB更新、装置コマンド送信など)
  • 例外の種類、HRESULT、Win32エラーコード

おすすめは 1行1イベントのJSON Lines形式。人間向けの長文より、後で複数ファイルを突き合わせられることが重要。

クリティカルイベントは同期的に書く

  • 細かいイベント: 非同期バッファでOK
  • Warning以上: 早めにフラッシュ
  • 重要な境界イベント: 同期的に残す(ProcessStart, ConfigLoaded, ExternalCommandSentなど)

最終クラッシュマーカーのルール

ここはフル機能ロガーを作る場所ではない。 1回だけ、短く、確実に残す。

入れる情報:

  • 発生時刻(UTC)
  • PID / TID
  • セッションID
  • バージョン / ビルド番号
  • どのフックから来たか(UnhandledExceptionなど)
  • 例外の種類または例外コード
  • 直前の操作ID

絶対にやってはいけないこと:

  • DIコンテナからロガーを取得する
  • async/awaitを使う
  • ロック待ちをする
  • UIダイアログを出す
  • 圧縮やHTTP送信をする

クラッシュハンドラでやることはこれだけ:

  1. 多重突入を防ぐ
  2. 1行だけ書く
  3. フラッシュする
  4. 終了する

フレームワーク別の注意点

  • WinForms: ThreadExceptionで継続させるのは危険。プログラムミス相手には向かない。
  • WPF: DispatcherUnhandledExceptionも同様。記録の入口として使う。
  • .NET共通: AppDomain.UnhandledExceptionは最後の通知。重い回復処理はしない。
  • ネイティブC++: SetUnhandledExceptionFilterだけでなく、_set_invalid_parameter_handlerset_terminateも拾う。

WER LocalDumpsを土台にする

WER LocalDumpsが一番扱いやすい。OS側でダンプを残せるから。

設定例:

reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v DumpFolder /t REG_EXPAND_SZ /d "C:\CrashDumps\MyApp" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v DumpCount /t REG_DWORD /d 10 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe" /v DumpType /t REG_DWORD /d 2 /f

ダンプとPDBはセットで保管する。 ダンプだけあっても、その時のEXE/DLLとPDBがなければ読めない。

監視プロセスを入れると何が変わるか

watchdog(監視プロセス)は以下を記録できる。

  • 子プロセス開始時刻と終了時刻
  • exit code(終了コード)
  • 再起動回数
  • ダンプの有無

これだけで「本当にクラッシュしたのか」「OSシャットダウンだったのか」「ユーザーが閉じたのか」が見えるようになる。

よくあるNGパターン

  1. catch(Exception)でログだけ出して続ける → 途中状態が残り、後続障害が増える
  2. 非同期ロガーのキューだけを信じる → 落ちた瞬間にキューごと消える
  3. クラッシュハンドラでHTTP送信する → DNSや認証の問題が落ちた文脈に乗る
  4. ダンプはあるが通常ログと結びつかない → sessionやPIDが共通でない
  5. WinForms/WPFの未処理例外イベントで延命する → ゾンビ状態になりやすい

最低限の導入チェックリスト

  • 通常ログが1行1イベントで残る
  • 全ログにUTC、PID、TID、version、sessionがある
  • 最終クラッシュマーカー専用ファイルがある
  • WER LocalDumpsがアプリ単位で設定されている
  • PDBと配布バイナリを保管している
  • 次回起動時に前回異常終了を検知できる
  • 検証機で意図的に落として、本当に残るか確認した

まとめ

「落ちる側のプロセスだけに期待しない」 これがすべて。

  • 通常ログ、最終クラッシュマーカー、OS/別プロセス側の証跡に分ける
  • クラッシュ時はローカルへ短く残すだけ
  • 重い処理は再起動後か別プロセスへ回す
  • WER LocalDumpsを土台にする
  • 継続より、記録して終了を基本にする

関連する記事

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

未想定例外発生時の対応判断フロー

想定していない例外が発生したとき、アプリを終了すべきか継続すべきかを判断するための実用的な指針をまとめた記事です。失敗単位、共有状態、外部副作用、ネイティブ境界の観点から3段階の選択肢と判断表で整理し、設計判断に直接使えます。

記事を読む

関連トピック

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

このテーマがつながるサービス

この記事は次のサービスページにつながります。近い入口からご覧ください。

ブログ一覧に戻る