Scott Mitchell、4GuysFromRolla.com
Atif Aziz、Skybow AG
September 2004
要約: HTTP モジュールと HTTP ハンドラを使用して ASP.NET アプリケーションにエラー ログ機能を追加する方法を、Scott Mitchell と Atif Aziz が説明します。サンプル プログラム ファイル内では実際のコメント行は英語で書かれていますが、この記事内では説明目的で日本語で書かれています。この記事には英語のページへのリンクも含まれています。
MSDNElmah.msi サンプル ファイルをダウンロードする
目次
はじめに
ELMAH: エラー ログのモジュールとハンドラ
HTTP ハンドラと HTTP モジュールの概要
ELMAH のアーキテクチャを調べる
ASP.NET Web アプリケーションに ELMAH を追加する
まとめ
参考資料
関連書籍
はじめに
ASP.NET アプリケーションを操作して、別の ASP.NET アプリケーションで簡単に再利用できる便利な機能または機能セットを設計したことがありますか。ASP.NET には、各種の機能をコンポーネント化するためのさまざまなツールが用意されています。再利用を目的とした最も一般的な ASP.NET のツールには、以下の 2 つがあります。
- ユーザー インターフェイス要素と機能を提供する、ユーザー コントロールおよびコンパイル済みカスタム サーバー コントロール
- ビジネス ロジックおよびデータ アクセス コードを提供する .NET クラス ライブラリ
あまり注目されることのない 2 つの ASP.NET 再利用ツールには、HTTP モジュールと HTTP ハンドラがあります。
HTTP ハンドラと HTTP モジュールについてご存知でなくても心配しないでください。これらについてはこの記事の後半で詳しく説明します。とりあえず、HTTP モジュールは、ASP.NET リソースへの要求時に発生するイベントに応答して実行するように構成可能なクラスであるということを理解しておいてください。HTTP ハンドラは、特定のリソースまたは特定の種類のリソースのレンダリングを行うクラスです。実際は、ASP.NET Web ページをプロジェクトに追加するたびに、実質的に 1 つの HTTP ハンドラが作成されることになります。これは、ASP.NET Web ページの HTML 部分が実行時に動的にコンパイルされるときに、HTTP ハンドラの実装である System.Web.UI.Page を直接または間接的に継承するためです。これは、インライン戦略と分離コード戦略のどちらを取るかにはまったく関係ありません。
おわかりのように、通常、ASP.NET アプリケーションは一連の Web ページで構成されています。これらのページは、エンド ユーザーのブラウザから要求されたときに呼び出されます。ASP.NET 開発者が作成するコードのほとんどは、特定の Web ページへの要求に固有のものです。たとえば、何らかの検索クエリに基づいてデータベースの結果を表示する、特定のページの分離コード クラス内にあるコードなどが挙げられます。ただし、1 つの Web ページに直接関連するコード、つまりアプリケーション内のすべてのページに適用されるコードを作成する必要が出てくることもあります。たとえば、各ユーザーが Web サイト内を移動する順序の追跡が必要なことがあります。これを行うには、各ページにおいて、要求が行われた時刻とユーザーを識別する情報を記録する必要があります。
このようなログ機能を提供する 1 つの方法として、密接な関係があるデータベース内のデータを記録したコードをサイトの各 Web ページの Page_Load イベント ハンドラに追加する方法があります。ただし、この方法は保守や再利用がかなり困難です。サイトに新しい ASP.NET ページを追加するたびに、適切なログのコードを挿入する必要があります。同じような機能を別のサイトに追加する場合は、そのサイトのすべてのページで必要なコードを追加する必要があります。ログ機能は、論理的にも物理的にも各ページの機能から独立しているのが理想的です。そうすれば、この機能を別のサイトに追加する作業は、アセンブリをサイトの /bin ディレクトリに配置するのと同じように単純なものとなります。
HTTP モジュールと HTTP ハンドラを使用すれば、このような再利用や保守が可能になります。この記事では、一連の HTTP モジュールと HTTP ハンドラを見ていきます。これらは、エラー ログの保守や再利用を容易にするために設計されています。この記事の目的は、HTTP ハンドラと HTTP モジュールを非常に高度なレベルのコンポーネント化の形式として使用し、Web アプリケーションから独立して、一連の全機能の開発、パッケージ化、および配置を単一のユニットとして可能にする方法を示すことです。この目的は、主に HTTP ハンドラと HTTP モジュールを使用した再利用やコンポーネント化の利点を得ることのできるアプリケーションを調べていくことで達成します。
ELMAH: エラー ログのモジュールとハンドラ
この記事で扱っているエラー ログのモジュールとハンドラ (ELMAH: Error Logging Modules And Handlers) は、共著者 Atif Aziz (http://www.raboof.com/
) が作成したもので、エラー ログ機能を ASP.NET Web アプリケーションに追加するための簡単な手段をもたらします。ELMAH は、HTTP モジュールと HTTP ハンドラを使用して、Web アプリケーションに直接関連するコードの高度なコンポーネント化 (アプリケーション全体のログなど) を実現します。ELMAH は、プラグ可能な真のソリューションです。つまり、再コンパイルや再配置を必要とせずに、実行中の ASP.NET Web アプリケーションに動的に追加できます。
特定の Web アプリケーションがどれほど綿密に作成およびテストされていても、トラブルが発生することがあります。問題はコードにあるのではなく、電子メール サーバーが応答しなかったり、データが壊れていて暗号化に失敗することなどもあります。原因が何であれ、例外が発生した場合、特にライブ サイトでは、その例外の詳細を記録して、問題の診断に役立てることが重要です。ELMAH には、集中化されたエラー ログと通知を行うためのメカニズムが用意されています。ASP.NET アプリケーション内で検出されない例外が発生するたびに、ELMAH が通知を受け取り、Web.config ファイルに記載されている方法で例外を処理します。この処理には、データベースへの例外の詳細の記録や管理者への電子メールの送信、またはその両方が含まれる場合があります。
ELMAH は、未処理の例外に対してスムーズな応答を行うようには設計されていません。その役割は、未処理の例外の詳細を単に記録することだけです。ELMAH を ASP.NET Web アプリケーションに追加すると、そのアプリケーションで発生した未処理の例外がすべてログに記録されます。未処理の例外が発生したときに、エンド ユーザーの操作に影響が及ぶことはありません。エンド ユーザーには依然として "サーバー エラー" ページが表示されるか、または HTTP 500 エラーを処理するように構成されているカスタム エラーが存在する場合は、さらにユーザーにわかりやすいメッセージを含むページにリダイレクトされます。ただし、その背景では、未処理の例外が発生したことが ELMAH によって検出され、詳細が記録されます。
ELMAH では、未処理の例外は HttpApplication オブジェクトの Error イベントを通じて検出されます。Error イベントは、未検出の例外が要求の処理中に発生するたびにトリガされます。この要求は、.NET クラス ライブラリから行われることも、ASP.NET Web ページから行われることもあります。多くの ASP.NET アプリケーションでは、Server.ClearError() メソッドを呼び出すことにより、カスタム エラー ページや処理を正しく実装しない場合があることに注意してください。このエラーをクリアすると、Error イベントが発生せず、クライアントへの報告も行われないため、ELMAH で例外をログに記録できなくなります。別の言い方をすれば、カスタム エラー ページで ClearError() を使用すると、ユーザーには問題が発生したことが示されますが、開発者にはそれがわからなくなります。
注: カスタム エラー ページの作成の詳細については、Eli Robillard の記事「Rich Custom Error Handling with ASP.NET (英語)」を参照してください。
注: ASP.NET Web サービスで未処理の例外が発生した場合、Error イベントは HTTP モジュールにも ELMAH にも報告されません。その代わりに、ASP.NET ランタイムによってインターセプトされ、SOAP エラーがクライアントに返されます。Web サービスでエラーを記録するには、SOAP エラーをリッスンする SOAP 拡張機能 を作成する必要があります。
ELMAH には、未処理の例外の詳細を記録するだけでなく、エラー ログを表示するための一連の HTTP ハンドラも用意されています。ログへの Web インターフェイスが用意されており、未処理のすべてのエラーを含む一覧と、特定のエラーに関する詳細が表示されます (図 1 と図 2 を参照)。
.gif)
図 1. エラー ログの表示
.gif)
図 2. エラーの表示
このエラー ログは RSS としてレンダリングすることもできるため、管理者は通常使用している RSS アグリゲータを使用して、エラーの発生時に通知を受け取ることができます (図 3 を参照)。
.gif)
図 3. エラーの RSS フィード
注: RSS (Really Simple Syndication) は、XML 形式の標準であり、ニュースなどの更新されるコンテンツを提供するために一般的に使用されます。RSS を使用したコンテンツの提供方法や Web ベースの RSS リーダーの作成方法など、RSS の詳細については、「Creating an Online RSS News Aggregator with ASP.NET (英語)」を参照してください。
説明を簡潔にするため、この記事では、ELMAH の一部の機能のみに触れ、主なコンポーネントだけを紹介します。この記事では、完全なコードのダウンロードが可能です。このコードを細かく見て、実装の詳細を理解することをお勧めします。また、http://workspaces.gotdotnet.com/elmah には、ELMAH 用の GotDotNet ワークスペースのセットアップが用意されています。このワークスペースは、ディスカッション、問題の報告、および最新の状態の保持を目的としています。
エラー ログの集中化を実現する既存のソリューション
ASP.NET にはエラー ログの記録や表示の機能は組み込まれていませんが、Microsoft の Patterns & Practices Group では、オープンソースのエラー ロガーである例外管理アプリケーション ブロック (EMAB: Exception Management Application Block) (英語) を作成しています。EMAB は、デスクトップと Web ベースの両方の .NET アプリケーションで動作するように設計されていますが、EMAB はむしろデスクトップ アプリケーション用に開発されたものであり、後から Web アプリケーションへの対応が追加されたという印象があります。その理由は、EMAB では既定により例外の詳細が Windows のイベント ログに発行されるためです。イベント ログは、デスクトップ アプリケーションにとっては簡潔な例外情報の保存に適したバッキング ストアですが、ほとんどの Web アプリケーション、特に Web ホスト会社の共有サーバーでホストされているアプリケーションでは、イベント ログは使用されていません。イベント ログを使用するには、特別なアクセス許可を得て、ASP.NET アプリケーションがイベント ログに書き込めるようにする必要があるためです。確かに EMAB には情報をデータベースに記録するカスタム発行者を作成できるだけの柔軟性が備わっていますが、その分、開発者のタスクが増えることになります。
注: ELMAH には Microsoft SQL Server 2000 用のデータベース記録モジュールが付属しています。このモジュールについては後で説明します。また、ELMAH ではカスタム例外ロガーを作成することもできます。たとえば、例外の詳細を Web サーバーのファイル システム上の XML ファイルに記録するロガーを作成できます。実際のところ、使用する EMAB 用のカスタム発行者を既に作成している場合は、ELMAH を拡張して例外管理アプリケーション ブロックを使用することもできます。
どのように EMAB を使用して例外情報を記録するかは、Web アプリケーションの保守性と再利用性に大きな影響を及ぼします。たとえば、例外情報を記録するための単純な方法として、try ...catch ブロックを各 ASP.NET Web ページの各コード ブロックの前後に配置し、EMAB を catch セクションで呼び出すことが考えられます。
private void Page_Load(object sender, EventArgs e)
{
try {
// 例外を発生させる可能性のあるコード。
}
catch (Exception ex) {
// 例外ロガー ライブラリを呼び出して例外情報を記録します。
}
}
これは無謀な方法です。なぜなら、例外のログ記録が各 ASP.NET Web ページに密結合されるため、保守や再利用ができなくなるためです。より優れた方法は、Global.asax 内の Application_Error イベントで EMAB を利用することです。この方法では、例外発行コードがどの ASP.NET Web ページとも関係を持たず、1 つの集中化された場所に配置されるため、保守や再利用が可能な疎結合されたアーキテクチャが提供されます。この方法の欠点は、プラグ不可であることです。このエラー ログ機能を別の ASP.NET Web アプリケーションに追加するには、そのアプリケーションの Global.asax を変更し、さらにアプリケーションの再コンパイルと再配置が必要になります。
この記事の目的は、EMAB の代わりとなるものを紹介することではありません。むしろ、HTTP ハンドラと HTTP モジュールによって実現可能なコンポーネント化について強調することです。ELMAH は、集中化されたエラー ログ記録などの一般的なタスクをコンポーネント化して、保守を容易にすると共に、高度な再利用を可能にする方法を示します。ELMAH の目的は、適用可能な機能のコンポーネント化に向けたガイダンスを提供することです。
HTTP ハンドラと HTTP モジュールの概要
ELMAH のアーキテクチャと実装について詳しく調べる前に、ここで HTTP ハンドラと HTTP モジュールについて見直してみましょう。IIS Web サーバーに要求が着信するたびに、IIS ではその要求の拡張子を調べて、どのように処理するかを判断します。HTML ページ、CSS ファイル、イメージ、JavaScript ファイルなどの静的なコンテンツの場合、IIS は要求を自ら処理します。ASP ページ、ASP.NET Web ページ、ASP.NET Web サービスなどの動的なコンテンツの場合、IIS は指定した "ISAPI 拡張機能" に要求を委任します。ISAPI 拡張機能とは、特定の種類の要求をレンダリングする方法を認識している一連のアンマネージ コードです。たとえば、ISAPI 拡張機能 asp.dll は従来の ASP Web ページへの要求のレンダリングを行い、ISAPI 拡張機能 aspnet_isapi.dll は ASP.NET リソースへの要求が着信すると呼び出されます。
IIS では、ISAPI 拡張機能だけでなく "ISAPI フィルタ" も使用できます。ISAPI フィルタは、IIS が発生させたイベントに応答して実行できる一連のアンマネージ コードです。IIS は要求を処理する間に、対応するイベントを発生させるいくつかのステップを経過します。たとえば、要求が IIS に初めて着信したとき、要求が認証されるとき、レンダリングされたコンテンツがクライアントに送り返されるときなどはイベントが発生します。通常、ISAPI フィルタは、URL 書き換え、圧縮、特殊な認証や承認、特殊なログなどの機能を提供するために使用されます。
ASP.NET リソースへの要求が IIS に着信すると、その要求は ASP.NET エンジンにルーティングされます。要求されたリソースのコンテンツはこのエンジンでレンダリングされます。ASP.NET エンジンは IIS と似たような機能を行います。つまり、要求が ASP.NET HTTP パイプラインを通過する間にいくつかのイベントを発生させます。また、ASP.NET エンジンは、要求されたリソースのレンダリングを特定のクラスに委任します。IIS ではアンマネージ ISAPI 拡張機能と ISAPI フィルタを使用しますが、ASP.NET では HTTP ハンドラと HTTP モジュールというマネージ クラスを使用します。
HTTP ハンドラは、特定の種類のリソースのレンダリングを行うクラスです。たとえば、ASP.NET Web ページの分離コード クラスは HTTP ハンドラです。このハンドラは、特定の Web ページのマークアップをレンダリングする方法を認識しています。ハンドラは、特定の種類のリソースのマークアップを作成する方法を認識している特殊なレンダラであると考えることができます。
注: HTTP ハンドラの詳細およびハンドラの実用的なアプリケーションについては、「Serving Dynamic Content with HTTP Handlers (英語)」を参照してください。
HTTP モジュールは、サーバー上で要求が処理される間に各段階を経過するときに発生するさまざまなイベントにアクセスできるクラスです。そのような ASP.NET アプリケーション イベントには、Error イベントがあります。これは、未処理の例外が発生したときにトリガされる、ELMAH が注意を向けているイベントです。
注: HTTP モジュールを使用して URL 書き換えを実装する方法などを含む HTTP モジュールの詳細については、「ASP.NET での URL 書き換え」を参照してください。
図 4 は、ASP.NET HTTP パイプラインを図で示しています。プロセスは要求が IIS に着信すると開始されることに注目してください。要求されたリソースは ASP.NET ISAPI 拡張機能が処理するように構成されているとすると、IIS は ISAPI 拡張機能であるアンマネージ aspnet_isapi.dll に要求をディスパッチします。この ISAPI 拡張機能は、要求をマネージ ASP.NET エンジンに渡します。要求の処理中には、登録されているモジュールと、それらがサブスクライブしているイベントに応じて、1 つ以上の HTTP モジュールが実行されることがあります。最後に、ASP.NET エンジンは、コンテンツのレンダリングを行う HTTP ハンドラを特定し、そのハンドラを呼び出して、生成されたコンテンツを IIS に返します。IIS では、それを要求元のクライアントに返します。
.gif)
図 4. エラー ロガーを経由するデータ フロー
ELMAH は、Error イベントのイベント ハンドラが用意されている HTTP モジュールを使用してエラー ログを集中化します。イベントが発生すると、ELMAH は例外の詳細を記録します。また、ELMAH では HTML マークアップや RSS マークアップを生成してエラー ログの情報を表示することを主な役割とする HTTP ハンドラを使用します。
既存の Web アプリケーションを構成してさまざまなハンドラやモジュールを利用する作業は、モジュールまたはハンドラ アセンブリを Web アプリケーションの /bin ディレクトリにコピーし、構成に関係するいくつかの行を Web.config ファイルに追加することで実現します。
Web アプリケーション用に HTTP モジュールを構成するには、<httpModules> セクションを、追加するモジュールの種類を指定する Web.config ファイルに含めます。
<httpModules>
<add name="ModuleName" type="ModuleType" />
</httpModules>
ModuleType はモジュールの種類を示す文字列であり、完全修飾クラス名 (Namespace.ClassName) とそれに続くアセンブリ名で構成されています。type 属性には、バージョンおよび言語に関する情報や、厳密な名前のアセンブリに必要な公開キー トークンを含めることもできます。次のコード スニペットは、ELMAH のエラー ログ モジュールを ASP.NET アプリケーションに含めるために使用する必要のある実際の <httpModules> 設定を示しています。
<httpModules>
<add name="ErrorLog" type="GotDotNet.Elmah.ErrorLogModule,
GotDotNet.Elmah, Version=1.0.5527.0, Culture=neutral,
PublicKeyToken=978d5e1bd64b33e5" />
</httpModules>
HTTP ハンドラは、<httpHandlers> セクションを Web.config ファイルに追加することによって、Web アプリケーションで使用できます。HTTP ハンドラは特定の種類のリソースのコンテンツをレンダリングするため、<httpHandlers> 要素には、type 属性だけでなく、この HTTP ハンドラにマッピングする必要のあるファイルのパスまたは拡張子をを示す path 属性が含まれています。さらに、verb 属性も存在します。この属性を使用すると、GET 要求や POST 要求と同様に、ハンドラの使用を特定の種類の HTTP 要求に制限できます。次の例では、拡張子が .ashx のファイルへのすべての要求に対して呼び出される HTTP ハンドラを作成します。
<httpHandlers>
<add verb="*" path="*.ashx" type="HandlerType" />
</ httpHandlers >
HTTP ハンドラの type 属性は、HTTP モジュールと同じ構文オプションを使用して表現されます。Web.config 内のこれらの設定は、machine.config ファイルに配置することもできます。そうすると、サーバー上のすべての Web アプリケーションのハンドラとモジュールが有効になります。次のコード スニペットは、この記事のダウンロード ファイルに含まれているデモで使用されている Web.config ファイル内の <httpHandlers> 要素を示しています。このコードは、/elmah/default.aspx への受信要求は ErrorLogPageFactory クラスを使用してレンダリングする必要があることを示しています。
<httpHandlers>
<add
verb="POST,GET,HEAD"
path="elmah/default.aspx"
type="GotDotNet.Elmah.ErrorLogPageFactory,
GotDotNet.Elmah, Version=1.0.5527.0, Culture=neutral,
PublicKeyToken=978d5e1bd64b33e5" />
</httpHandlers>
おわかりのように、HTTP モジュールと HTTP ハンドラを ASP.NET Web アプリケーションに追加する作業は非常に単純であり、わずか数秒で実行できます。ASP.NET アプリケーションの再コンパイルや再配置も不要です。そのため、HTTP モジュールと HTTP ハンドラは再利用に最適なツールであり、疎結合された保守性の高い要素にアプリケーションをコンポーネント化する手段を提供します。
ELMAH のアーキテクチャを調べる
ELMAH のアーキテクチャは、以下の 3 つのサブシステムで構成されています。
- エラー ログ記録サブシステム
- HTTP モジュール サブシステム
- HTTP ハンドラ サブシステム
エラー ログ記録サブシステムは、エラーをログに記録するタスクとエラー情報をログから取得するタスクの 2 つを行います。HTTP モジュール サブシステムは、未処理の例外が ASP.NET アプリケーションで発生したときに、エラーをログに記録します。HTTP ハンドラ サブシステムは、エラー ログをマークアップにレンダリングする手段を提供し、エラー ログへの Web ベースのインターフェイスと RSS フィードで構成されています。
図 5 に示すように、HTTP モジュールと HTTP ハンドラのサブシステムは、どちらもエラー ログ記録サブシステムを利用します。HTTP モジュール サブシステムは、例外情報をエラー ログ記録サブシステムに送信します。その一方で、HTTP ハンドラ サブシステムは、そのエラー情報を読み取ってレンダリングします。
.gif)
図 5. エラー ログ記録システムの位置付け
ELMAH のアーキテクチャについてさらに理解を深めるために、これらの 3 つのサブシステムを詳しく見てみましょう。
エラー ログ記録サブシステム
エラー ログ記録サブシステムは、エラーをログに記録し、特定のエラーまたはエラーのサブセットに関する詳細を取得する機能を提供します。この機能は、以下のようなクラスによって実現されます。
- ErrorLog: この抽象クラスは、ログの読み取りおよび書き込みの両方の規定されたメソッドを提供します。
- Error: このクラスは、特定のエラーの詳細を記述するプロパティを含みます。
- ErrorLogEntry: このクラスは、特定の
ErrorLogに対する特定のErrorインスタンスを表します。ErrorLogEntryは、実質的にErrorインスタンスと、生成元のErrorLogインスタンスをグループ化します。
これらの 3 つのクラスと、これらが HTTP モジュールおよび HTTP ハンドラ サブシステムとどのように連携し、集中化された完全な例外ログ記録ユーティリティを提供するかについて見てみましょう。
ErrorLog クラスを調べる
特定のプロジェクトの設定または戦略によっては、エラー ログに別のバッキング ストアを採用する必要があります。たとえば、運用サーバーでは、例外を Microsoft SQL Server に記録する必要がありますが、開発サーバーでは、エラーを一連の XML ファイルまたは Microsoft Access データベースに保存するだけで十分な場合があります。別のバッキング ストアを使用できるようにするために、エラー ログ記録サブシステムには、すべての ELMAH エラー ロガーが実装する必要のある基本メソッドを定義する抽象基本クラス ErrorLog が用意されています。これらのメソッドは以下のとおりです。
- Log(Error): エラーをバッキング ストアに記録します。
Errorクラスは、未処理の例外に関する情報を表します。このErrorクラスについては後で詳しく説明します。エラー情報をログに記録する場合、Log()メソッドでは一意の ID をエラーに割り当てる必要もあります。 - GetError(id): ログ内の特定のエラーに関する情報を返します。
- GetErrors(...): ログからエラーのサブセットを返します。このメソッドは、すべてのエラーを一度に表示するのではなく、エラー ログをページ単位で表示するために HTTP ハンドラ サブシステムによって使用されます。
ELMAH には、以下の 2 つの ErrorLog の実装が付属しています。
- SqlErrorLog:
System.Data.SqlClientプロバイダを使用して、エラーを Microsoft SQL Server 2000 データベースに記録します。SqlErrorLogには SQL Server 2000 が必要です。これは、SQL Server 2000 の一部の XML 機能を利用するためです。ただし、このような実装の詳細は変更が可能です。 - MemoryErrorLog: エラーをアプリケーションのメモリ (RAM) に記録します。つまり、各アプリケーションが独自のプライベート ログを受け取るように、MemoryErrorLog は AppDomain に関連付けられます。言うまでもありませんが、このログはアプリケーションの再起動後やアプリケーションの有効期間後は残らないため、通常は他の実装が失敗したときのテストや一時的なトラブルシューティングの目的のみに役立ちます。
これらの例外ロガーは、どちらも数行程度のテキストを ASP.NET Web アプリケーションの Web.config ファイルに追加するだけで使用できます。エラーの詳細を SQL Server またはアプリケーションのメモリ以外の場所に保存する必要がある場合は、独自のカスタム ロガーを作成できます。ELMAH 用のエラー ロガーを実装するには、ErrorLog を拡張するクラスを作成し、Log()、GetError()、および GetErrors() の実装を必要なストアに対して提供します。
ELMAH の HTTP モジュールと HTTP ハンドラのサブシステムは、どちらも指定した ErrorLog クラス (SqlErrorLog、MemoryErrorLog、または独自のカスタム ログ クラス) と直接対話します。HTTP モジュールでは、Error インスタンスを作成し、それを ErrorLog メソッドの Log() メソッドに渡すことで、例外情報をログに記録します。HTTP ハンドラは、ErrorLog の GetError() メソッド (特定の ErrorLogEntry インスタンスを返します) と GetErrors() メソッド (一連の ErrorLogEntry インスタンスを返します) を使用して、1 つ以上のエラーに関する詳細を読み取ります。
Error クラスについて
ErrorLog の Log() メソッドは、Error 型の入力パラメータを予期します。ここでは、カスタム Error クラスが .NET Framework に付属の Exception クラスの代わりに使用されます。Exception クラスは、アプリケーションの有効期間中のコード スタック全体にわたる例外情報のやり取りにより適しているためです。ただし、Exception オブジェクトには保存、入力、および移植性に関する問題があるため、例外ログへの保存には適していません。確かに、バイナリ シリアル化を利用して Exception インスタンスを保存することはできますが、それには同じ型とアセンブリが用意されたマシンで Exception オブジェクトを逆シリアル化する必要があります。これは、ログとその内容が特定のランタイムまたは構成を持つマシンで表示できるだけでなく、移植性がある必要があるため、特に管理と運用という点において許容のできない制限です。さらに、Exception インスタンスには、現在の Web 要求の ServerVariables コレクションの値など、Web アプリケーションに固有の関連情報が欠けていることがよくあります。このような情報は、診断に不可欠な可能性があります。つまり、簡単にまとめると、Error クラスは、すべての種類の例外のサロゲートとして機能し、Web アプリケーションで発生した例外の情報を保持します。
表 1 は、Error のすべてのプロパティを示しています。
| プロパティ | 説明 |
|---|---|
| Exception | このエラーによって表される Exception インスタンス。これは単なるランタイム プロパティであり、クラスのインスタンスと共に保持されることはありません。 |
| ApplicationName | このエラーが発生したアプリケーションの名前。 |
| HostName | このエラーが発生したホスト マシンの名前。適切な既定値は Environment.MachineName です。 |
| Type | エラーの種類、クラス、またはカテゴリ。これは通常、例外の完全な種類名 (アセンブリ修飾以外) です。 |
| Source | エラーの発生元。通常は、Exception オブジェクトの Message プロパティと同じです。 |
| Message | エラーを記述した簡単なテキスト。通常は、Exception オブジェクトの Message プロパティと同じです。 |
| Detail | 完全なスタック トレースなどの、エラーの詳細テキスト。 |
| User | エラーの発生時にアプリケーションにログインしていたユーザー。Thread.CurrentPrincipal.Identity.Name によって返されたユーザーなどです。 |
| Time | エラーの発生日時。常に現地時間です。 |
| StatusCode | エラーの結果として応答ヘッダーに返されるステータス コード。たとえば、FileNotFoundException の場合は 404 になります。残念ながら、ASP.NET 内ではこの値を信頼性の高い方法で常に判断できるとは限りません。場合によっては、この StatusCode 値がゼロと報告されることがあります。 |
| WebHostHtmlMessage | カスタム エラー ページが存在しなかった場合に Web ホスト (ASP.NET) が生成する既定の HTML メッセージ。 |
| ServerVariables | Web サーバー変数 (HttpRequest.ServerVariables に含まれる変数など) の NameValueCollection。 |
| QueryString | HTTP クエリ文字列変数 (HttpRequest.QueryString に含まれる変数など) の NameValueCollection。 |
| Form | フォーム変数 (HttpRequest.Form に含まれる変数など) の NameValueCollection。 |
| Cookies | クライアントから送信された Cookie (HttpRequest.Cookies に含まれる Cookie など) の NameValueCollection。 |
WebHostHtmlMessage プロパティについては、さらに説明が必要です。ASP.NET Web アプリケーションが未処理の例外を検出した場合に、アプリケーションがカスタム エラー ページを使用するように構成されていないときは、図 6 に示すような画面が表示されます。これは、ASP.NET 開発者であればだれでも頻繁に見かける画面です。
.gif)
図 6. 標準のエラー ページ
例外が発生すると、対応する画面の実際の HTML マークアップへのアクセスが行われ、Error クラスの WebHostHtmlMessage プロパティに保存されます。特定の例外に関する詳細情報を示すこのページへのアクセスが行われると、対応する Error インスタンスにその WebHostHtmlMessage プロパティの値が存在する場合は、実際の例外情報画面 (図 6 のような画面) を示すページのリンクが表示されます。ここで便利なのは、例外がログに記録されるだけでなく、ログを後で調べるときに ASP.NET が生成した元のエラー ページにもアクセスできることです。このような作業はすべて、カスタム エラーが有効な場合に可能です。
Error クラスには、状態を XML 形式にシリアル化したり、XML 形式から逆シリアル化するためのメソッドも用意されています。詳細については、付属のコード内の FromXml と ToXml を参照してください。
ErrorLogEntry クラス: Error と ErrorLog とを関連付ける
エラー ログ記録サブシステムの最後のクラスは、ErrorLogEntry クラスです。このクラスは、Error インスタンスと ErrorLog インスタンスとを関連付けます。HTTP ハンドラ サブシステムが GetError() メソッドを呼び出して特定の例外に関する情報を取得すると、GetError() メソッドは特定のバッキング ストアから情報を取得して、その情報を ErrorLogEntry インスタンスに挿入します。ErrorLogEntry クラスには、以下の 3 つのプロパティが含まれています。
- Id: 例外の詳細の一意の ID。
- Log: バッキング ストアを表す
ErrorLogインスタンスへの参照。 - Error: 特定のエラーの詳細を含む、
Errorクラスの作成されたインスタンス。
GetError() メソッドは 1 つの ErrorLogEntry インスタンスを返しますが、GetErrors() は ErrorLogEntry インスタンスの一覧を返します。GetErrors() は、エラーを一度に n レコード分ページングできるように特別に設計されています。
図 7 は、ELMAH のアーキテクチャの新たなビューで、エラー ログ記録サブシステムをさらに詳細に示しています。
.gif)
図 7. 更新されたアーキテクチャ
HTTP モジュール サブシステム
ELMAH は、2 つの HTTP モジュール (ErrorLogModule および ErrorMailModule) で構成されています。ErrorLogModule は、アプリケーションの Error イベントのイベント ハンドラを作成する HTTP モジュールです。未処理の例外が発生した場合、HTTP モジュールは、アプリケーションの構成で指定された適切なエラー ロガーを取得し、Log() メソッドを呼び出し、例外および現在の要求の HttpContext の情報で作成された Error インスタンスを渡します。次のソース コードは、ErrorLogModule クラスの密接な関係があるコードを示しています。
public class ErrorLogModule : IHttpModule
{
public virtual void Init(HttpApplication application)
{
application.Error += new EventHandler(OnError);
}
protected virtual ErrorLog ErrorLog
{
get { return ErrorLog.Default; }
}
protected virtual void OnError(object sender, EventArgs args)
{
HttpApplication application = (HttpApplication) sender;
LogException(application.Server.GetLastError(),
application.Context);
}
protected virtual void LogException(Exception e,
HttpContext context)
{
try
{
this.ErrorLog.Log(new Error(e, context));
}
catch (Exception localException)
{
Trace.WriteLine(localException);
}
}
}
ErrorLogModule の実行は、Init() メソッドから始まり、Error イベントが発生するたびに OnError() メソッドを呼び出す必要があることを ASP.NET ランタイムに示します。OnError() メソッドは、HttpApplication オブジェクトを参照し、LogException() メソッドを呼び出し、最後の例外の詳細と特定の要求に固有の HttpContext インスタンスを渡します。LogException() は、単に適切な ErrorLog クラスの Log() メソッドを呼び出し、新しい Error インスタンスを渡します (Error インスタンスのコンストラクタは、Exception インスタンスと HttpContext インスタンスを取得し、対応するプロパティを設定します。詳細については、ダウンロード ファイルに含まれているソース コードを参照してください)。
ErrorLogModule には、読み取り専用の ErrorLog プロパティが含まれ、ErrorLog.Default が返す ErrorLog インスタンスを返します。Default は、ErrorLog クラスの ErrorLog 型の静的なプロパティです。Web アプリケーションの構成を参照して、例外のログ記録に使用するクラス (SqlErrorLog、MemoryErrorLog、または例外のログ記録のカスタム クラス) を特定します。
注: 「ASP.NET Web アプリケーションに ELMAH を追加する」では、特定の例外ロガーを使用するための Web アプリケーションの構成方法について説明します。この構成は簡単で、Web.config ファイルまたは machine.config ファイルにいくつかの行を追加するだけです。
HTTP モジュール サブシステムのその他の HTTP モジュールは ErrorMailModule クラスであり、例外が発生すると、管理者に電子メールを送信します。ELMAH のこの情報については説明しませんが、この記事のダウンロード ファイルに含まれているコード サンプルで、このモジュールの使い方を確認することができます。
HTTP ハンドラ サブシステム
HTTP ハンドラの目的は、特定の種類のリソースのコンテンツをレンダリングすることです。要求が ASP.NET HTTP パイプラインに入ると、ASP.NET エンジンは要求されたパスを調べて、要求されたリソースの処理に使用する HTTP ハンドラを特定します。つまり、HTTP ハンドラまたは HTTP ハンドラ "ファクトリ" のどちらかが処理する特定のパスを持つように ASP.NET アプリケーションを構成することができます。HTTP ハンドラ ファクトリは、コンテンツのレンダリングを直接行うのではなく、代わりに HTTP ハンドラ インスタンスを選択して返すクラスです。ここで返された HTTP ハンドラ インスタンスが、要求されたリソースのレンダリングを行います。
ELMAH の HTTP ハンドラ サブシステムは、ログに記録されたエラーを表示するマークアップを生成するために設計されたいくつかの HTTP ハンドラ クラスと、1 つの HTTP ハンドラ ファクトリ クラスで構成されています。HTTP ハンドラ ファクトリ クラス ErrorLogPageFactory は、要求された URL の PathInfo の部分を調べて、出力を生成する必要のある HTTP ハンドラを特定します。
注: URL の PathInfo の部分は、後ろにファイル名が続く任意の追加コンテンツであり、Request オブジェクトの PathInfo プロパティを通じて取得することができます。たとえば、http://www.example.com/someDir/somePage.aspx/somePath という URL の場合、PathInfo の部分は "somePath" です。URL のさまざまな構成要素で使用される用語、および対応する Request オブジェクトのプロパティの詳細については、Rick Strahl
の blog の「Making Sense of ASP.NET Paths
」を参照してください。
次のコード スニペットは、HTTP ハンドラ ファクトリ クラス ErrorLogPageFactory のさらに興味深いコードを示しています。
public class ErrorLogPageFactory : IHttpHandlerFactory
{
public virtual IHttpHandler GetHandler(HttpContext context,
string requestType, string url, string pathTranslated)
{
string resource =
context.Request.PathInfo.Length == 0 ? string.Empty :
context.Request.PathInfo.Substring(1);
switch (resource.ToLower(CultureInfo.InvariantCulture))
{
case "detail" :
return new ErrorDetailPage();
case "html" :
return new ErrorHtmlPage();
case "rss" :
return new ErrorRssHandler();
default :
return new ErrorLogPage();
}
}
}
おわかりのように、ErrorLogPageFactory クラスの GetHandler() メソッドは、要求の PathInfo に基づいて HTTP ハンドラ インスタンスを返します。PathInfo が rss の場合は、HTTP ハンドラ ErrorRssHandler のインスタンスが返され、ログが RSS フィードとしてレンダリングされます。PathInfo が detail の場合は、HTTP ハンドラ ErrorDetailPage のインスタンスが返され、特定の例外の情報が表示されます。
ASP.NET Web アプリケーションの設定では、HTTP ハンドラ ファクトリ ErrorLogPageFactory にマッピングするパス (ErrorLog.aspx など) を指定する必要があります。例外ログの RSS フィードを表示するには、http://www.example.com/ErrorLog.aspx/rss を参照します。
ELMAH のさまざまな HTTP ハンドラ クラス (ErrorDetailPage、ErrorHtmlPage、ErrorRssHandler、ErrorLogPage など) は、異なるマークアップをレンダリングします。たとえば、HTTP ハンドラ ErrorRssHandler は、最新の 15 個のエラーをループ処理し、適切な XML マークアップを出力してこの情報を RSS 形式で表示します。その他の HTTP ハンドラはすべて直接または間接的に System.Web.UI.Page クラス (ASP.NET 分離コードのすべてのクラスの派生元のクラス) から派生します。これらのページ関連の HTTP ハンドラは、Page クラスの Render() メソッドと OnLoad() メソッドをオーバーライドして、ログに記録された例外のページング可能な一覧を表示する HTML インターフェイスを作成します。これらのページのスクリーンショットについては、前の図 1、2、3 を参照してください。
注: Error クラスは ServerVariables、QueryString、Form、Cookie の各コレクションを保存しますが、ServerVariables コレクションだけが例外の詳細に表示されます。これは、QueryString のパラメータと Cookie が、ServerVariable の QUERY_STRING パラメータと HTTP_COOKIE パラメータのそれぞれを通じて表示できるためです。Form コレクションは、通常は大部分の診断では役に立たない数十 KB のビュー状態の情報しか含まれていない可能性があるため省略されます。もちろん、必要に応じて、HTTP ハンドラの詳細を変更してこの情報を含めることは簡単です。
これで、ELMAH の 3 つのサブシステムの説明が完了しました。次は、既存の ASP.NET Web アプリケーションに ELMAH を追加する方法を見てみましょう。特に、任意のサイトに ELMAH を追加することがどれほど簡単か (HTTP ハンドラと HTTP モジュールが提供するコンポーネント化による利点) に注目してください。
ASP.NET Web アプリケーションに ELMAH を追加する
ELMAH は ASP.NET Web アプリケーションに非常に簡単に追加できます。次の 2 つの手順に従います。
- Web アプリケーションに ELMAH アセンブリを追加する。
- ELMAH の HTTP モジュールと HTTP ハンドラを使用するように Web アプリケーションを構成する。
ELMAH は、Web サーバー上の特定の Web アプリケーションに適用することができます。手順としては、アセンブリを Web アプリケーションの /bin ディレクトリにコピーし、Web.config ファイルを使用して ELMAH の設定を構成します。さらに、Web サーバー上のすべての Web アプリケーションに適用するように ELMAH を構成することもできます。その手順は、アセンブリを Web サーバーのグローバル アセンブリ キャッシュ (GAC) に追加し、同じ構成設定を Web.config ではなく machine.config に追加します。
Web.config (または machine.config) ファイルには、以下の設定を追加する必要があります。
- 例外情報のログ記録に関する情報を格納する内部セクション
<errorLog>を持つ新しいセクション名<gotdotnet.elmah>を定義する、<configSections> 要素内の <sectionGroup> 要素 - ELMAH で使用する例外ロガーの型参照と、その例外ロガーに固有のすべての設定を格納する内部セクション
<errorLog>を含む、<gotdotnet.elmah>セクション - ブラウザからの参照時にエラー ログの各種のビューをレンダリングするパスを示す
<httpHandlers>セクションのエントリ - ASP.NET HTTP パイプラインに
ErrorLogModuleを追加する<httpModules>セクションのエントリ
Web.config ファイル内の次のスニペットは、この記事のダウンロード ファイルに含まれており、これらの 4 つの項目の設定方法を示しています。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<!-- Web.config に新しいセクション グループを許可します。 -->
<sectionGroup name="gotdotnet.elmah">
<!-- セクション グループ内部に errorLog セクションがあることを
示します。 -->
<section name="errorLog"
type="System.Configuration.SingleTagSectionHandler,
System, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
</sectionGroup>
</configSections>
<!-- このセクション グループには、使用する例外ロガーの種類
(SqlErrorLog、MemoryErrorLog、またはカスタム ロガー) が含まれます。
また、例外ロガーに関連するプロパティ
(SqlErrorLog の場合は connectionString) も含まれます。 -->
<gotdotnet.elmah>
<errorLog type="GotDotNet.Elmah.SqlErrorLog,
GotDotNet.Elmah, Version=1.0.5527.0, Culture=neutral,
PublicKeyToken=978d5e1bd64b33e5"
connectionString="...接続文字列..." />
</gotdotnet.elmah>
<system.web>
<!-- aspnetham/errorlog.aspx への要求は HTTP ハンドラ ファクトリ
ErrorLogPageFactory によって処理される必要があることを登録します。 -->
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah/default.aspx"
type="GotDotNet.Elmah.ErrorLogPageFactory,
Skybow.Samples.AspNetHam, Version=1.0.5527.0,
Culture=neutral, PublicKeyToken=978d5e1bd64b33e5" />
</httpHandlers>
<!-- HTTP パイプラインに HTTP モジュール ErrorLogModule を追加します。 -->
<httpModules>
<add name="ErrorLog" type="GotDotNet.Elmah.ErrorLogModule,
GotDotNet.Elmah, Version=1.0.5527.0, Culture=neutral,
PublicKeyToken=978d5e1bd64b33e5" />
</httpModules>
...
</system.web>
</configuration>
<configSections> 要素内の <sectionGroup> 要素は、構成ファイル <gotdotnet.elmah> に追加のセクション グループがあることを示します。さらに、このカスタム セクション内部に <errorLog> セクションがあることを示します。実際の <gotdotnet.elmah> 要素の内部には、使用するエラー ログ実装を指定する <errorLog> 要素があります。ELMAH には、2 つの組み込み実装 (SqlErrorLog と MemoryErrorLog) が付属しています。<errorLog> 要素では、どちらの実装を使用するかを指定することができます。また、作成済みのカスタム例外ロガーを使用するように指定することもできます。さらに、<errorLog> 要素には、エラー ログ実装に固有の設定も保持されます。たとえば、<errorLog> 要素で SqlErrorLog の使用を指定した場合は、その実装にデータベースへの接続方法を通知する connectionString プロパティを含める必要があります。適切なテーブルと関連するストアド プロシージャを作成する SQL スクリプトは、ダウンロード ファイルに含まれています。
注: 未処理の例外が発生した場合に管理者に電子メールで通知するには、<errorMail> という新しい要素を定義する別の <section> 要素を <sectionGroup> に追加する必要があります。さらに、実際の <gotdotnet.elmah> 要素には、<errorMail> 要素を追加する必要があります。この構文のサンプルについては、ダウンロード ファイルに含まれている Web.config ファイルを参照してください。
<httpHandlers> セクションでは、ErrorLogPageFactory (HTTP ハンドラ ファクトリ) を使用して、コンテンツをレンダリングしてエラー ログを表示する HTTP ハンドラを取得する必要があることを指定します。path 属性の値は、エラー ログ表示に到達するために必要な、アプリケーションの仮想ルートの相対 URL を示します。この URL は変更可能ですが、ASP.NET エンジンで処理される拡張子を含む URL である必要があります。つまり、パスを errors.log のように変更する場合は、errors.log への要求を ASP.NET ISAPI 拡張機能 (aspnet_isapi.dll) にマッピングするように IIS を構成する必要があります。管理者だけがログを表示できるようにする場合は、ASP.NET の URL 承認機能を使用して、特定のユーザーや一連のユーザーまたはロールへのアクセスを制限します。一方、ログへの Web ベースのアクセスを完全に無効にするには、<httpHandlers> セクションを構成しないようにします。
<httpModules> セクションでは、ASP.NET HTTP パイプラインに HTTP モジュール ErrorLogModule を追加します。この <httpModules> の設定が含まれていることを確認してください。含まれていない場合、ELMAH は Error イベントをリッスンしないため、未処理の例外がログに記録されません。
おわかりのように、既存の ASP.NET Web アプリケーションに ELMAH を追加することは非常に簡単です。ELMAH の簡単な配置と再利用性は、ELMAH が HTTP モジュールと HTTP ハンドラを使用してコンポーネント化されているという事実によるものです。
まとめ
HTTP ハンドラと HTTP モジュールが ASP.NET Web アプリケーションに直接関連する機能をコンポーネント化するためのすばらしいツールであることが、おわかりいただけたと思います。集中化されたアプリケーション全体のログやアプリケーション全体の要求の監視などの一般的なタスクは、ハンドラとモジュールを通じてコンポーネント化することができます。この機能をラップして一連のコンポーネントを作成して、既存のコードやアプリケーションの移行、統合、または再コンパイルを行わずに、再利用性、保守性、および配置上の利点を得ることができます。
HTTP モジュールと HTTP ハンドラで可能なコンポーネント化の例を示すために、ELMAH、集中化されたエラー ログ、およびメール アプリケーションについて調べました。ELMAH では、HTTP モジュールを使用してアプリケーション全体の Error イベントをリッスンします。このイベントは、未処理の例外の結果として発生します。未処理の例外が発生すると、ELMAH はその例外を SQL Server データベース、メモリ、またはその他のバッキング ストアに記録します。また、ELMAH は例外のコンテンツを 1 人以上の受信者 (開発者やオペレーション スタッフなど) に電子メールで送信することもできます。
ELMAH には、HTTP モジュールだけでなく、Web ベースのメディアでエラー ログを簡単に表示できるようにするための一連の HTTP ハンドラと HTTP ハンドラ ファクトリも含まれています。従来の Web ページだけでなく、RSS フィードも該当します。ELMAH では、表示機能を HTTP ハンドラにラップすることによって独立したコンポーネントを管理します。これは、Web アプリケーションにそれらの情報を表示する ASP.NET Web ページを含めるように要求するのとは対照的です。HTTP ハンドラを使用して ELMAH を配置することは簡単なプロセスです。Web アプリケーションを再コンパイルしたり、ASP.NET Web ページを運用サーバーにアップロードしたりする必要はありません。
ELMAH は、HTTP ハンドラと HTTP モジュールが提供するコンポーネント化の機能の一例にすぎません。おそらく、他にもハンドラとモジュールによるコンポーネント化の利点を利用したアプリケーション全体のプロセスを実装された方がおられるでしょう。
プログラミングのご成功をお祈りします。
謝辞
この記事を MSDN の編集者に提出するにあたり、多くのボランティアの方々に校正および記事の内容や表現に関するフィードバックの提供をお願いしました。校閲の作業に従事してくださった Milan Negovan、Carl Lambrecht、Dominique Kuster、Roman Mathis、Raffael Zaghet、Muhammad Abubakar、Patrick Schuler の各氏にお礼を申し上げます。
参考資料
- <customErrors> 要素
- [HOW TO] Visual C# .NET を使用して ASP.NET のカスタム エラー報告ページを作成する方法
- ASP.NET HTTP ランタイム
- HTTP Modules (英語)
- ASP.NET Connection Model and Writing Custom HTTP Handler/Response Objects (英語)
関連書籍
- ASP.NET Data Web Controls Kick Start
- ASP. NET: Tips, Tutorials, and Code
- ASP.NET Unleashed
- Programming Microsoft ASP.NET
Atif Aziz は、Microsoft プラットフォームのソリューション開発に約 13 年携わっています。彼は、Skybow AG で上席コンサルタントを務めており、主に顧客の .NET 開発プラットフォームのソリューションに対する理解とソリューションの構築を支援しています。また、定期的に Microsoft 開発者コミュニティにも貢献してくれています。具体的には、Microsoft やそれ以外のカンファレンスで講演を行ったり、技術出版物の記事を書いたりしています。彼は、INETA の議長および最大のスイスの .NET ユーザー グループ (dotMUGS)
の代表も務めています。Atif のメール アドレスは atif.aziz@skybow.com です。また、彼の Web サイト (http://www.raboof.com/
) からもコンタクトできます。
ASP/ASP.NET 関連の 5 冊の書籍の著者であり、4GuysFromRolla.com の創設者でもある Scott Mitchell は、1998 年から Microsoft の Web テクノロジに取り組んでいます。現在、フリーランスのコンサルタント、トレーナー、およびライターとして活動しています。Scott のメール アドレスは mailto:mitchell@4guysfromrolla です。また、http://scottonwriting.net/
の彼の blog からもコンタクトできます。