導入:なぜ「エラーの発生場所」だけでは足りないのか
実務におけるシステム運用で最も頭を悩ませるのは、「何が起きたか」はわかるのに「なぜ起きたか」が特定できないエラーです。ログに「File not found」とだけ出力されても、それが「どのユーザーの」「どのリクエストから発生したものか」が分からなければ、再現手順の特定に膨大な時間を要します。Annotated Exceptions(注釈付き例外)は、例外に文脈(コンテキスト)を付加して再スローすることで、デバッグの質を劇的に向上させる手法です。
基礎知識:例外のラップ(Wrapping)とは
例外のラップとは、発生した低レベルの例外を、より上位のレイヤーで「捕まえて」、詳細な情報を付加した新しい例外として「投げ直す」技術です。関数型プログラミングの文脈では、副作用を管理しつつエラーを伝播させる際に重要となります。単なるエラーメッセージの連結ではなく、構造化されたデータとしてコンテキストを持たせることが、後のログ解析を容易にする鍵となります。
実装:コンテキストを付加する論理構造
実装のアプローチはシンプルです。元の例外(Cause)を保持したまま、新しい例外インスタンスを作成します。この際、現在の関数の引数や実行コンテキストをデータとして保持させます。これにより、スタックトレースを追うだけでなく、エラー発生時の具体的なパラメータを確認することが可能になります。
サンプルプログラム:TypeScriptによる実装例
以下は、Node.js等の環境で利用できる、コンテキストを付加する例外クラスの実装例です。
// 詳細なコンテキストを保持するためのカスタム例外クラス
class AnnotatedError extends Error {
constructor(public message: string, public context: Record<string, any>, public cause?: Error) {
super(message);
this.name = 'AnnotatedError';
}
}
// ユーザー情報に基づいてファイルを処理する関数
async function processUserFile(userId: string, filename: string) {
try {
// ここでエラーが発生すると仮定
throw new Error('File not found');
} catch (err) {
// 発生元のエラーをラップし、コンテキスト(userId等)を付加して再スロー
throw new AnnotatedError(
'ユーザーファイルの処理中にエラーが発生しました',
{ userId, filename },
err as Error
);
}
}
// 実行とログ出力
async function main() {
try {
await processUserFile('user_123', 'data.json');
} catch (err) {
if (err instanceof AnnotatedError) {
// 構造化されたログとして出力することで、監視ツールでの検索性が向上
console.error('エラー詳細:', {
message: err.message,
context: err.context,
originalError: err.cause?.message
});
}
}
}
main();
応用・注意点:現場で陥りやすい罠
過剰なラップに注意: 全ての関数で例外をラップすると、スタックトレースが冗長になりすぎ、逆に可読性を損ないます。ビジネスロジックの境界や、外部APIとの通信など「エラーの理由を特定したい重要な箇所」に絞って適用するのがコツです。
機密情報の混入: コンテキストにユーザーのパスワードや個人情報をそのまま含めないよう注意してください。ログ管理基盤に転送する前に、フィルタリングを行うか、必要な識別子(IDなど)のみを付加する運用ルールを徹底しましょう。この手法を取り入れるだけで、障害対応時の「手探り状態」から脱却できるはずです。

コメント