.NET による分散アプリケーションの構築
Kenny Jones、Edward Jezierski
Microsoft Corporation
August 2001
日本語版最終更新日 2001 年 12 月 5 日
要約 : このドキュメントでは、.NET テクノロジを使用する例外管理システムの設計および実装 ガイドラインについて説明します。.NET アプリケーションの中で、保守可能性とサポート可能性の高い方法で例外を処理するプロセスに焦点を当てます。
目次
例外管理
例外処理プロセス
例外の検出
例外の伝播
カスタム例外
未処理例外の管理
例外情報の隠蔽
情報の収集
ログの記録
通知
テクノロジ上の具体的な注意事項
例外管理
保守とサポートの容易な柔軟性の高いアプリケーションを構築するためには、適切な例外管理戦略を採用する必要があります。システムの設計にあたっては、以下の機能を持たせなくてはなりません。
- 例外の検出。
- 情報のログ記録と報告。
- システム運用を支援するための、外部から監視できるイベントの生成。
早い段階で時間を費やして、明快で一貫性のある例外管理システムを設計することにより、開発の際にそのようなシステムを組み合わせたり、さらに悪い場合は、既存のコード ベースに例外処理を強引に埋め込む必要がなくなります。
例外管理システムは、適切にカプセル化し、ログの記録と報告を抽象化してアプリケーションのビジネスロジックから切り離さなくてはなりません。また、アプリケーションの現在の健康状態とステータスに関する情報を与えるような、オペレータによる監視が可能なメトリックスを生成できなくてはなりません。これにより、発生している問題を迅速かつ正確にオペレータに通知し、開発者とサポートサービス部門に問題解決のための貴重な情報を提供できるアプリケーションを作成することができます。
例外と例外階層について
Microsoft® .NET 共通言語ランタイム (CLR) は、例外を生成するモジュールが、例外の処理を行うモジュールとは異なる言語で書かれている場合でも、プログラムに対して例外を統一的な方法で通知することができるプラットフォームを用意しています。
例外は、コード内の暗黙の仮定に対する違反を表しています。たとえば、コードが存在を仮定しているファイルにアクセスしようとしたが、そのファイルが存在しなかった場合には、例外が送出されます。しかし、コードがそのファイルの存在を仮定しておらず、先にファイルの存在をチェックしているようなシナリオでは、例外は必ずしも生成されません。
例外は必ずしもエラーとは限りません。例外がエラーを表すかどうかは、その例外が発生したアプリケーションによって決定されます。ファイルが発見できなかったときに送出される例外がエラーを表すかどうかは、シナリオによって異なります。
すべての例外クラスは、共通言語仕様 (CLS) に準拠して、System 名前空間内の Exception 基本クラスから派生されなくてはなりません。.NET Framework クラス ライブラリ (FCL) は、さまざまなタイプの一般的な例外を処理するために、網羅的な例外階層を用意していますが、これらはいずれも最終的には Exception から派生しています。例外は .NET ランタイムが生成することもあり、プログラム的に作成することもできます。独自の例外階層を作成する方法については、このドキュメントで後に詳しく説明します。
ApplicationException は、すべてのアプリケーション固有の例外クラスの基本クラスとなります。これは Exception から派生していますが、拡張機能は持っていません。カスタム アプリケーション例外は ApplicationException から派生させます。図 1 は、基本的な例外クラス階層を示しています。

図 1. 例外クラス階層
例外処理プロセス
例外の処理には主に 2 つのプロセスがあります。図 2 のプロセスフローチャートは、アプリケーションが例外を処理するために実行すべき基本的なステップを示しています。アプリケーションコード内の個々のメソッドやプロシージャは、このプロセスに従い、例外処理がカレントメソッドのスコープ内で定義された適切なコンテキストの中で行われるようにしなくてはなりません。

図 2. 例外処理プロセス
このプロセスは、例外が呼び出しスタック上を伝播する過程で繰り返し生じます。「例外の検出」と「例外の伝播」のセクションでは、このプロセスについて詳しく説明しています。図 3は、例外が最後のポイントまたは境界にまで伝播したときにアプリケーションが実行すべきプロセスを示しています。この時点で、アプリケーションはユーザーに返す前に例外の処理を行うことができます。

図 3. 未処理例外の処理
このプロセスは、アプリケーションが例外情報を永続化し、運用担当者に問題を通知し、ユーザーエクスペリエンスを適切に管理する上できわめて重要な役割を果たしています。このプロセスについては、このドキュメントの「未処理例外の管理」、「情報の収集」、「ログの記録」、および「通知」の各セクションで説明しています。
例外の検出
.NET Framework では、すべての .NET 言語が構造化例外処理を利用することができます。構造化例外処理は、例外、保護されたコード ブロック、およびフィルタを含む制御構造によって、例外処理を堅牢かつ効率的に行えるようにします。try、catch、および finally ブロックを使って、自分のコード内で送出された例外を検出し、適切に対処することができます。
try
{
// 例外を送出する可能性のある何らかのコード
}
catch(SomeException exc)
{
// 例外の発生に
// 対処するためのコード
}
finally
{
// 例外が送出されたかどうかにかかわらず、
// つねに実行されるコード。
// これは通常は、例外が送出されたか
// どうかにかかわらず実行される
// クリーンアップ コードである。
}
try ブロックは、例外を送出する可能性のあるコードを含んでいます。このブロック内で例外が送出されると、その例外のクラスにマッチするフィルタを持つ最初の catch ブロックが例外をキャッチします。フィルタは catch キーワードの後の括弧内に指定されます。複数の catch ブロックを置く場合には、具体的な型から一般的な型の順に並べるようにしてください。これにより、どの例外についても、最も具体的な型の catch ブロックが実行されることが保証されます。finally ステートメントは、例外が送出されたかどうかにかかわらず実行され、クリーンアップやその他の必ず実行されなくてはならないコードを実行します。
例外のキャッチは、以下のいずれかのアクションを実行しなくてはならないときにのみ行います。
- ログの記録のために情報を収集する
- 例外に何らかの関連情報を追加する
- クリーンアップ コードを実行する
- 復旧を試みる
これらのうちのどのアクションも実行しないメソッドは、例外をキャッチするのではなく、その例外を呼び出しスタック上で後方に伝播させるべきです。このように、特定のメソッドのスコープ内で処理する必要がある例外のみをキャッチし、その他の例外はすべて伝播させることで、クリーンで明示的なコードを書くことができます。
例外は適切に使用する
例外の送出は、コードの仮定に外れた条件が発生したときにのみ行います。言い換えると、意図した機能を提供するための手段として例外を使用してはなりません。たとえば、ユーザーはアプリケーションにログオンするときに無効なユーザー名やパスワードを入力することがあります。この場合、ログオンは成功しませんが、これは予期された有効な結果であり、例外が送出されるべきではありません。一方、ユーザーデータベースが使用不可能であるというような予期しない条件が発生したときには、例外を生成します。例外の送出は、単に呼び出し元に結果を返すよりもコストがかかります。このため、コード内の通常の実行の流れを制御する目的で例外を使用するべきではありません。また、例外を多用すると、読みにくく、管理が難しいコードになってしまう可能性があるので注意してください。
CLR がインターセプトする例外
シナリオによっては、送出した例外がランタイムによってキャッチされ、別の型の例外が元の例外の代わりに呼び出しスタック上に送出されることがあります。たとえば、オブジェクトの ArrayList の Sort メソッドを呼び出す場合を考えてみましょう。いずれかのオブジェクトが IComparable インターフェイスの CompareTo メソッドの中で例外を送出する場合、その例外はランタイムによってキャッチされ、Sort メソッドを呼び出したコードに対して System.InvalidOperationException 例外が送出されます。さらに、リフレクションを通して呼び出したメソッドによって送出されたすべての例外は、ランタイムによってキャッチされ、コードに対して System.Reflection.TargetInvocationException が送出されます。これらのシナリオでは、元の例外は失われません。これはランタイムによって送出される例外の InnerException として設定されます (InnerException プロパティについては「例外の伝播」のセクションで説明します)。このような状況はさまざまなシナリオで起こりえます。この点を理解し、このようなシナリオの影響を最小限に抑えるために、アプリケーションを徹底的にテストするようにしてください。
これらのトピックの詳細については、以下を参照してください。
例外の伝播
例外を伝播させる方法は主に 3 つあります。
- 例外に自動的に伝播させる
このアプローチでは、プログラマは何も行わず、例外を意図的に無視します。この場合、制御は例外型にマッチするフィルタを持つ catch ブロックが見つかるまで、現在のコード ブロックから呼び出しスタックを遡上します。 - 例外をキャッチし、再送出する
このアプローチでは、プログラマは例外をキャッチして対処を行った後に、カレント メソッドのスコープ内でクリーンアップを行うか、その他の必要な処理を行います。例外から復旧できない場合は、同じ例外を呼び出し元に対して再送出します。 - 例外をキャッチし、ラッピングし、ラッピングした例外を送出する
例外が呼び出しスタックを伝播するにつれ、例外型の具体性は徐々に低下していきます。例外をラッピングすると、より具体的な例外を呼び出し元に返すことができます。この様子を図 4 に示します。このアプローチでは、プログラマは例外をキャッチし、カレントメソッドのスコープ内で対処し、クリーンアップやその他の必要な処理を行うことができます。復旧ができない場合は、例外を新しい例外にラッピングし、新しい例外を呼び出し元に送出します。Exception クラスの InnerException プロパティを使うと、以前にキャッチした例外を明示的に保存することができます。これにより元の例外を、新しい、より具体性のある外側例外の中に、内側例外としてラッピングすることができます。InnerException プロパティは例外クラスのコンストラクタの中で設定されます。

図 4. 例外の伝播
例外が呼び出しスタック上を伝播するとき、catch ブロックは外側例外のみをキャッチすることができます。内側例外は InnerException プロパティを通してプログラム的にアクセスすることができますが、catch ブロックとのマッチングは行われません。
次の例は、これらのメソッドの実装方法を示しています。
try
{
// 例外を送出する可能性のある何らかのコード
}
catch(TypeAException e)
{
// 必要な処理を実行するためのコード
// 例外を再送出する。
throw;
}
catch(TypeBException e)
{
// 必要な処理を実行するためのコード
// 現在の例外を、より具体性のある外側例外にラッピングし、
// 新しい例外を再送出する。
throw(new TypeCException(strMessage, e));
}
finally
{
// 例外が送出されたかどうかにかかわらず、つねに
// 実行されるコード
}
最初の catch ブロックは TypeAException 型の例外をキャッチし、必要な処理を実行し、同じ例外を呼び出しスタック上に再送出します。第 2 の catch ブロックは、TypeBException をより具体性のある TypeCException 型の外側例外にラッピングし、新しい外側例外を呼び出しスタック上に再送出する方法を示しています。この例のコードは、TypeAException 型と TypeBException 型の例外のみを処理します。その他のすべての例外型については、このコードは例外を自動的に伝播させています。
| | 例外に対処することができる | より具体的な例外にすることができる |
| 例外を自動的に伝播させる | いいえ | いいえ |
| 例外をキャッチし、再送出する | はい | いいえ |
| 例外をキャッチし、ラッピングし、ラッピングした例外を送出する | はい | はい |
InnerException を使用する状況
例外をラッピングするのは、そのための十分な理由があるときに限ってください。例外が最初に送出されるときには、例外の具体的な原因に関する情報が提供されます。例外が呼び出しスタック上を伝播する過程で、例外型の具体性は低下していきます。例外をラッピングすることによって、呼び出し元により具体的な例外を提供することができます。
たとえば、LoadUserInfo という架空のメソッドを考えてみましょう。このメソッドは、アクセスを試みたときに存在すると仮定されるファイルから、ユーザーの情報をロードするものとします。ファイルが存在しなければ、FileNotFoundException が送出されます。この例外は、LoadUserInfo メソッドの中で、ある特定の意味を持っています。しかし、この例外が呼び出しスタック上を伝播し、たとえば LogonUser メソッドに到着したとき、FileNotFoundException 例外は意味のある情報を何も提供しなくなっています。FileNotFoundException を (後に説明する) カスタム例外クラス、たとえば FailedToLoadUserInfoException という名前の例外クラスにラッピングし、ラッパー例外を送出すれば、呼び出し元の LogonUser メソッドにとってより意味のある情報を提供することができます。LogonUser メソッドの中では、FailedToLoadUserInfoException 例外をキャッチし、この例外型に対処するようにすれば、別のメソッドの実装上の細部でしかない FileNotFoundException をキャッチする必要はなくなります。
カスタム例外
.NET Framework は、HRESULT などのメソッドの戻りコードを使用するのではなく、例外型をもとに識別を行う型ベースのシステムです。プログラマは、図 5 に示すように、ApplicationException を継承するアプリケーション固有の例外クラスのカスタム階層を確立する必要があります。

図 5. アプリケーション例外階層
この階層は、アプリケーションにとって以下の利点を持っています。
- 基本アプリケーション例外クラスでプロパティとメソッドを定義し、これを他のアプリケーション例外クラスで継承することができるため、開発が簡単になります。
- アプリケーションの導入後に作成される新しい例外クラスは、階層内の既存の例外型から派生させることができます。新しい例外オブジェクトの基本クラスの1 つをキャッチする既存の例外処理コードは、既存のコードに変更を加えなくても新しい例外をキャッチし、それをオブジェクトの基本クラスの 1つとして解釈します。
アプリケーション例外階層の設計
.NET Framework は、例外クラスの網羅的な階層を用意しています。.NET Frameworkが適切な例外クラスをすでに提供している場合には、新しいクラスを作成するのではなく、既存のクラスを使用してください。コード内で対処と処理を必要とする例外型のための新しいアプリケーション例外クラスを作成するのは、アプリケーション例外階層または Framework内に適切なクラスがない場合に限ってください。ほとんどのアプリケーション例外階層はかなりフラットなものになり、グループ化は整理の目的のため、または少数のアプリケーション例外のセットで共通のプロパティまたは機能を継承できるようにするために使用されることになるでしょう。
独自の階層を作成する際には、以下の質問をもとに、新しい例外クラスを作成する必要があるかどうかを判断してください。
- この条件で対処する例外は存在するか?
現在の階層または Framework の中に例外が存在する場合には、独自の例外クラスを作成するのではなく、既存の例外を使用します。 - その例外に特別な処理は必要か?
必要ならば、コードでその例外をキャッチし、明示的に処理が行えるように、新しい例外クラスを作成するべきです。これにより、より一般的な例外をキャッチし、条件付きロジックを使って実行すべきアクションを決定する必要がなくなります。 - その例外について特殊な振る舞いや追加の情報が必要か?
必要ならば、新しいアプリケーション例外クラスを使って、個々のニーズに合わせて情報や機能を追加することができます。
アプリケーション例外階層は、アプリケーション コード全体から参照できる 1 つのアセンブリ (大きなシステムの場合は少数のアセンブリのグループ) に格納するようにしてください。これにより、例外クラスの管理と導入を集中化することができます。
カスタム例外クラスの作成
名前の標準化を図るために、カスタム例外クラスの名前はつねに Exception という名前で終わらせるようにします。また、個々のカスタム例外クラスについて、次のクラス定義に示す 3 つのコンストラクタを用意しておくことをお勧めします。
using System;
public class YourBaseApplicationException : ApplicationException
{
// デフォルト コンストラクタ
public YourBaseApplicationException ()
{
}
// 1 つの文字列メッセージを受け付けるコンストラクタ
public YourBaseApplicationException (string message) : base(message)
{
}
// 1 つの文字列メッセージと、このカスタム例外クラスによって
// ラッピングされる内側例外を
// 受け付けるコンストラクタ
public YourBaseApplicationException(string message,
Exception inner) : base(message, inner)
{
}
}
基本アプリケーション例外クラスの作成
アプリケーションは、ApplicationException から派生する 1 つの基本例外クラスを持つべきです。このクラスは、前に図 5 に示したように、アプリケーションのすべての例外クラスの基本クラスの役割を果たします。
この基本クラスにフィールドを追加して、例外が発生した日付と時刻やマシン名といった具体的な情報をキャプチャするようにします。これにより、例外に関する一般的な詳細情報が 1 つの基本クラスにカプセル化され、他の例外クラスはこの情報を継承を通して利用できるようになります。
カスタム例外のリモート管理
Exception クラスは ISerializable インターフェイスを実装しており、シリアル化を独自に管理することができます。例外をリモーティング境界上でマーシャリングできるようにするためには、クラスに [Serializable] 属性を追加し、次に示すコンストラクタを追加する必要があります。
protected YourBaseApplicationException(SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
例外が ApplicationException 基本クラスにフィールドを追加している場合は、これらの値をシリアル化されたデータ ストリームにプログラム的に永続化する必要があります。これは GetObjectData メソッドをオーバーライドし、次に示すようにこれらの値を SerializationInfo オブジェクトに追加することによって実現できます。
[Serializable]
public class ExampleException : ApplicationException
{
public ExampleException() : base()
{
}
public ExampleException(string message) : base(message)
{
}
public ExampleException(string message,Exception inner) :
base(message,inner)
{
}
protected ExampleException(SerializationInfo info, StreamingContext context) :
base(info,context)
{
m_strMachineName = info.GetString("m_strMachineName");
}
public override void GetObjectData( SerializationInfo info,
StreamingContext context )
{
info.AddValue("m_strMachineName", m_strMachineName,
typeof(String));
base.GetObjectData(info,context);
}
private string m_strMachineName = Environment.MachineName;
public string MachineName
{
get
{
return m_strMachineName;
}
set
{
m_strMachineName = value;
}
}
}
SerializationInfo オブジェクトの値は、例外オブジェクトのコンストラクタの中で、info.GetValue か、SerializationInfo オブジェクトのいずれかの Get メソッド (上に示した GetStringなど)を使って取得することができます。カスタム例外は、ローカルとリモートの境界上でマーシャリングされるときに、自分の状態を保持できなくてはなりません。アプリケーションが現時点でリモーティング境界を越えていない場合でも、シリアル化をサポートすることで、アプリケーションが後に変更された場合でも例外情報が失われないようにすることができます。例外のカスタム値をプログラム的にシリアル化していないと、クライアントで逆シリアル化(deserialize)されたときに、それらの値は正しく設定されません。また、シリアル化をサポートすることで、アプリケーションは例外に関するすべての情報をシリアル化し、ログの記録の目的のために格納できるようになります。
これらのトピックの詳細については、以下を参照してください。
未処理例外の管理
未処理例外が、例外がユーザーに返される前の、アプリケーションが例外処理を行える最後のポイントまたは境界にまで伝播したとき、アプリケーションはもはや例外から復旧することはできません。この時点で、アプリケーションは情報を収集し、情報をログに記録し、必要な通知を送信し、クリーンアップを実行し、その他の必要な処理を行った上で、例外条件をユーザーに伝えなくてはなりません。ほとんどの Web ベース アプリケーションでは、この境界はWeb ページとエンド ユーザー、または Web サービスと他のアプリケーションの間に存在し、ASP.NETによって制御されます。このセクションでは、システム境界で未処理例外を管理するためのツールについて説明します。
ASP.NET
ASP.NET では、スクリプティング言語の貧弱な機能に頼る必要はなくなっています。ASP.NET のコードはコンパイルされ、任意の.NET Framework 言語で書くことができます。従来の ASP の世界のテクニックの貧弱さとは対照的に、ASP.NETのページはコンポーネントと同じあらゆる例外処理テクニックを利用することができます。ASP.NETはいくつかの具体的な機能により、アプリケーションが例外を管理し、ユーザーに対する情報の表示方法を構成できるようにしています。
Web.config 設定
例外管理設定は、アプリケーションの Web.config ファイルの中で構成します。次に、Web.config ファイルの中の例外設定の例を示します。
<customErrors defaultredirect="http://hostname/error.aspx" mode="on">
<error statuscode="500" redirect="/errorpages/servererror.aspx" />
<error statuscode="404" redirect="/errorpages/filenotfound.htm" />
</customErrors>
customErrors 要素では、デフォルト リダイレクト ページを指定します。デフォルト リダイレクト ページには 3 つのモードがあります。
- on
未処理例外は、ユーザーを指定された defaultredirect ページにリダイレクトします。これは主にプロダクション環境で使用されます。 - off
ユーザーに対して例外情報が表示され、defaultredirect ページへのリダイレクトは行われません。これは主に開発環境で使用されます。 - remoteonly
ローカル マシン上のサイトに (localhost を使って) アクセスするユーザーのみに例外情報が表示され、その他のすべてのユーザーは defaultredirect ページにリダイレクトされます。これは主にデバッグのために使用されます。
デフォルト リダイレクト ページに加えて、特定の HTTP エラー コード用のページを設定することができます。たとえば、すべての 404エラーで特定のエラー ページが表示され、すべての 500 エラーで別のページが表示されるようにすることができます (上の例を参照)。
.NET 以前には、これらの設定は Internet Information Services (IIS)メタベースの中で構成する必要がありました。ASP.NET の Web.config ファイルは、アプリケーションの構成設定を 1つのファイルに集中化し、アプリケーションとその設定の xcopy 導入を可能にしています。
これらの設定は ASP.NET ファイルにのみ適用されることに注意してください (.aspx や.asmxなどの特定の拡張子のファイル)。たとえば、ユーザーが Web サーバー上に存在しない.htm ファイルや.aspファイルを要求した場合、IIS は Web.config ファイルではなく IIS メタベースに定義されている HTTPエラー設定に基づいてユーザーをリダイレクトします。ASP.NET ファイルと非 ASP.NET ファイルの間に一貫性を持たせたい場合は、IISと Web.config ファイルを、ユーザーを同じページにリダイレクトするように構成します。IISメタベースの中のすべての設定を、アプリケーション ファイルとともに導入しなくてはなりません。
@ Page ディレクティブ
Web.config ファイルの設定は、そのディレクトリとすべての子ディレクトリに適用されます。これらの設定は、Page ディレクティブの ErrorPage 属性を使って、特定のページについてオーバーライドすることができます。次に例を示します。
<%@ Page ErrorPage="customerror.aspx" %>
このサンプルは、Web.config の設定にかかわらず、現在のページの中で未処理の例外が発生した場合には、ユーザーを customerror.aspx にリダイレクトします。
ASP.NET 例外の処理
ASP.NET は、アプリケーション コードから伝播した未処理例外に対処するためのイベントを主に 2 つ用意しています。
- Page_Error
このイベントは、例外がページ レベルで処理されなかったときに発生します。このイベントを発生させるためには、ページのコード内に Page_Error イベントを含め、@ Page ディレクティブの AutoEventWireup 属性が true (規定値) に設定されているかどうかを確認するか、次のコードを使ってイベントを手動でフックアップします。Page.Error += new System.EventHandler(Page_Error);
- Application_Error
これは global.asaxファイルに置かれており、そのためにアプリケーション全体のスコープを持っています。この例外は、特定のページによって例外が処理されなかった場合に発生します。このイベントは、ユーザーにエラーを手際よく返す前に、ログの記録、通知、およびその他の必要な処理を実行するために使用します。// global.asax ファイル
protected void Application_Error(Object sender, EventArgs e)
{
Exception exc = Server.GetLastError();
// ログの記録の実行、通知の送信などを行う。
}
これらのイベントでは、Server.GetLastError メソッドを使って、未処理例外オブジェクトへの参照を取得します。Server.ClearError を使って例外をクリアし、それ以降の伝播を止めることもできます。たとえば、ページに、Server.GetLastError を使って未処理例外オブジェクトにアクセスする Page_Error イベントがあったとします。Server.ClearError を使って例外をクリアしなければ、未処理例外は Application_Error イベントまで伝播します。Application_Error 内で Server.ClearError を使って例外をクリアすると、未処理例外は存在しなくなるため、web.config ファイル内の defaultredirect 設定はユーザーをリダイレクトしなくなります。defaultredirect に正しくユーザーをリダイレクトさせたい場合は、Application_Error 内で例外をクリアしないようにしてください。
これらのトピックの詳細については、以下を参照してください。
Web サービス
Web サービスは、例外の伝播を、Simple Object Access Protocol (SOAP) を使ってしか行えません。.NET Framework は、SOAP を使って例外を提起する唯一の手段として、SoapException クラスを用意しています。CLR は、異常な形式の SOAP 要求をクライアントから受け取ったときに、SoapException を自動的に送出します。
Web サービス メソッドが未処理例外を送出したとき、その例外は SoapException として再パッケージ化され、SOAP 応答を介して Web サービス クライアントに返されます。ServerFaultCodeにより、メッセージの形式や内容以外の条件を原因とするエラーが、要求の処理中に発生したことが示されます。サーバー ランタイムが作成する SOAP応答は標準の SOAP 形式になっており、非.NET クライアントもエラーが発生したことを検出することができます。.NETベースのクライアントでは、Framework が SOAP メッセージをキャプチャし、SoapException を逆シリアル化(deserialize) するので、クライアントは Webサービス以外の呼び出しの場合と同じように、標準の例外処理テクニックを使って例外を検出することができます。ただし、クライアント上で検出される例外は、Web サービスがもともと送出した例外型ではなく、SoapException になります。元の例外に関する情報は、SoapException の Message プロパティに含まれています。
例外情報の隠蔽
外部の企業とシステムに公開される Webサービスでは、例外情報の一部をクライアントに公開したくない場合があります。クライアントが対処できるように例外を送出したいが、その例外の詳細を社外に漏らしたくはない、つまりサービスで問題が発生しているという事実だけを示す例外を送出したいということです。
このような場合には、外部に知らせたい情報を含んだ総称アプリケーション例外を作成します。Webサービス内で未処理例外をキャッチしたら、例外をログに記録し、必要な処理を行った後に、総称アプリケーション例外の新しいインスタンスを作成します。その後、総称アプリケーション例外に必要な情報を設定し、クライアントに送出します。これにより、詳細情報を Webサービスにログを記録し、詳しい情報を含んでいない例外をクライアントに送出することができます。
詳細については、.NET Framework クラス ライブラリ の SoapException クラスのドキュメンテーションを参照してください。
UnhandledExceptionEventHandler デリゲート
UnhandledExceptionEventHandler デリゲートは、アプリケーション コードによって処理されない例外を処理するための手段として使用することができます。UnhandledExceptionEventHandler デリゲートに関連付けたメソッドは、未処理例外が発生したときに実行されます。
ほとんどの ASP.NET Web アプリケーションでは、アプリケーションが例外を処理するための最後のチャンスとして、UnhandledExceptionEventHandler ではなく Application_Error を使用することになります。
詳細については、.NET Framework クラス ライブラリ の UnhandledExceptionEventHandler デリゲートのドキュメンテーションを参照してください。
情報の収集
つねに、例外条件を正確に表すすべての該当情報をキャプチャするようにしてください。この情報をどのように扱うかは、情報の受信者のニーズに依存します。この情報の潜在的な受信者としては、以下のものが考えられます。
- エンド ユーザー
この人々は、意味のある、適切に提示された記述を必要とします。 - アプリケーション開発者
この人々は、問題の診断に役立つ、より詳しい情報を必要とします。 - オペレータ
この人々は、適切に対処し、必要な復旧手順を実行するための関連情報を必要とします。
適切な情報をキャプチャする
各グループが、それぞれにとって意味のある情報を受け取り、適切に対処できるようにすることが重要です。すべての情報がすべてのオーディエンスにとって適切であるとは限りません。たとえば、ユーザーに対して、ソースコードの行番号や、インデックスが範囲を超えたなどのプログラム上の条件を含んだ意味不明なメッセージを表示するべきではありません。エンドユーザーを同じほど困惑させるのが、「エラーが発生しました。システム管理者に連絡してください」というような一般的に過ぎるメッセージです。ユーザーに対しては、問題の解決につながるアクションを含む、何らかの役立つメッセージが表示されなくてはなりません。つねにすべての関連情報をキャプチャし、受信者のニーズに基づいて情報をフィルタリングした上で表示するようにしてください。
| オーディエンス | 必要な情報 |
|---|
| エンド ユーザー | 要求が成功したかどうか。 何が成功しなかったのかを示す、適切な形式のメッセージ。 問題を修正するために行うべきアクションを知らせる指示。 |
| アプリケーション開発者 | 例外が発生した日付と時刻。 例外のソースの正確な位置。 発生した具体的な例外。 例外に関連する情報と、例外が発生したときのシステムの状態。 |
| オペレータ | 例外が発生した日付と時刻。 例外のソースの正確な位置。 発生した具体的な例外。 通知すべき人と、知らせるべき情報。 運用上の問題なのか、開発上の問題なのかを示す例外型。 |
アプリケーションが各種のグループに応じて調整できるリッチな情報を提供できるように、次の表に示す詳細情報をキャプチャするようにしてください。「ソース」の欄は、この情報を生成できるクラスとクラス メンバを示しています。特に記していない限り、すべてのクラスは System 名前空間に含まれています。
| データ | ソース |
|---|
| 例外の日付と時刻 | DateTime.Now |
| マシン名 | Environment.MachineName |
| 例外のソース | Exception.Source |
| 例外型 | Object.GetType から取得される Type.FullName |
| 例外メッセージ | Exception.Message |
| 例外スタック トレース | Exception.StackTrace - このトレースは、例外が送出された時点から開始され、呼び出しスタック上で伝播される過程でポピュレートされていきます。 |
| 呼び出しスタック | Environment.StackTrace - 呼び出しスタック全体。 |
| アプリケーション ドメイン名 | AppDomain.FriendlyName |
| アセンブリ名 | System.Reflection 名前空間の中の AssemblyName.FullName |
| アセンブリ バージョン | AssemblyName.FullName に含まれています。 |
| スレッド ID | AppDomain.GetCurrentThreadId |
| スレッド ユーザー | System.Threading 名前空間の中の Thread.CurrentPrincipal |
すべての例外データへのアクセス
例外はさまざまな構造を持ち、例外の原因に関する貴重な情報を提供するさまざまなパブリック プロパティを公開することができます。何が異常を来したのかを完全に把握できるように、これらの情報すべてをキャプチャすることが重要です。(System.Reflection 名前空間で実装されている) リフレクションを使用することで、例外のパブリック プロパティのすべての値に簡単にアクセスすることができます。これは、Exception型の引数を受け付ける汎用的なログ記録ルーチンに便利です。このようなルーチンは、リフレクションを使って、詳細で具体的な例外情報を問い合わせ、アクセスすることができます。開発者が例外のソースを判断できるように、例外に関する十分な詳細情報をキャプチャすることが重要となります。
これらのトピックの詳細については、以下を参照してください。
ログの記録
例外の検出と関連情報の収集に加えて、アプリケーションには情報を一貫性のある信頼の置ける方法で格納させることが必要です。例外情報が適切にログが記録されていないと、例外の原因を理解するのがきわめて困難に、ときには不可能になります。多くの監視システムは、アプリケーションのログを使用して、システムの問題がいつ起きたのかを判断しています。監視アプリケーションは、ログにアクセスし、情報のフィルタリングを行った上で、適切なオーディエンスに送信することができます。
情報の格納にはいくつかのオプションがあり、それぞれが長所と短所を持っています。オプションとしては以下のものが考えられます。
- Windows イベント ログ
- Microsoft SQL Server™ 2000 などの中央のリレーショナル データベース
- カスタム ログ ファイル
Windows イベント ログの使用
Windows 2000 および Windows NT® オペレーティング システムは、いくつかのイベントログのセットを提供しています。たとえば、オペレーティング システムはエラーの詳細をシステム イベントログに格納しますが、アプリケーションが使用するためのアプリケーション イベント ログも用意されています。また、共有のアプリケーション イベントログを使用する代わりに独立したイベント ログを作成することで、アプリケーションのログ エントリを分離することができます。イベント ログは、1つのマシン上のエラー、警告、および情報メッセージのための一貫性のある中央のストレージ リポジトリの役割を果たします。.NETFramework はログの記録と監視を容易にするイベント ログ関連のクラスを持っています。
長所
- 情報をログに記録するための、きわめて信頼性の高い手法です。
- Windows 2000 と Windows NT の両方で提供されています。
- ログ ファイル サイズを管理する機能があります。ユーザーは最大ログ サイズを構成し、レコードが保持される日数を指定するか、ログ ファイルを手動でクリアするよう指定することができます。
- イベント ビューアなどのツールを使って、ログ エントリを表示し、操作することができます。
- System.Diagnostics 名前空間の EventLog クラスなどの.NET Framework クラスにより、イベント ログのプログラムからの書き込みと保守を簡単に行えます。
- ほとんどの監視ツールは、イベント ログをイベント ソースとしてサポートしています。
- イベント ログは Windows Management Instrumentation (WMI) と緊密に統合されています。
- システム管理者とオペレータは、一般にイベント ログとその関連ツールに慣れています。
- オペレーティング システムと多くのアプリケーションはイベント ログに記録するので、システム イベントとアプリケーション イベントのシーケンスを簡単に比較することができます。
短所
- 個々のマシンがローカル イベント ログに記録します。このため、大規模なサーバーファームでは、監視と保守の点で問題が生じることがあります。別のマシン上のログにエントリを書き込むこともできますが、この方法は障害のリスクが高くなるので使わないようにしてください。
Microsoft Application Center Server やいくつかのサードパーティ ソフトウェアパッケージでは、複数のサーバーのローカル イベント ログを集中化することができます。この場合には、複数のイベント ログを 1つのエンティティとして表示することができます。
Windows イベント ログの使用方法の詳細については、以下の Web サイトを参照してください。
中央のリレーショナル データベースの使用
イベント ログが個々のマシンに対応しているという問題を解決するために、SQL Server 2000 などの集中化されたデータベースを使って情報を格納することができます。
長所
- すべての情報が 1 つの集中化された位置に格納され、リモート マシンからアクセスできます。
- データベース ツールを使ってエラー データの問い合わせとレポート作成を行うことができます。
- 情報をアプリケーション固有の構造を持たせたテーブルに格納することができます。
短所
- データベースへのログの記録にはリスク要素があります。システムがネットワーク障害などがデータベースにアクセスできなくなった場合には、情報を失うリスクが生じます。例外が確実にログに記録されるようにするために、データベースの更新が失敗した場合には、その情報をローカル イベント ログに書き込むという方法も考えられます。しかし、これを行うと 2 つの位置を監視し、2つの異なるエラー形式を扱わなくてはならないため、理想的なソリューションとは言えません。
- アプリケーション エントリはオペレーティング システムのエントリとは別の位置に格納されます。このため、アプリケーション ログとオペレーティング システムのログの間でイベントのシーケンスを比較するのが困難になることがあります。
- データの表示と管理のためのツールを開発する必要があります。
- 監視システムを、データベースと対話を行えるようにカスタマイズしなくてはなりません。
カスタム ログ ファイルの使用
一部のアプリケーションは、情報をカスタム ログ ファイルにログを記録します。このソリューションは、イベント ログが何らかの具体的な記録上のニーズを満たせないシナリオでのみ使用するようにしてください。
長所
短所
- 同時アクセスを処理できるカスタムログ記録メカニズムの作成は簡単なことではありません。
- ログ サイズを管理するためのプロセスを作成し、実装しなくてはなりません。
- ログ ファイルの表示と管理のためのツールを開発する必要があります。
- Microsoft Application Center Server を含む多くの監視ツールはカスタム ログ ファイルを統合できないので、大規模なサーバー ファームでは監視と保守に関連する問題が生じることがあります。
- 監視ツールを、ログ ファイルを監視するように構成する必要があります。
MSMQ による非同期配信
上記のオプションとの組み合わせとして、Microsoft Message Queue (MSMQ) を使って、データを非同期的にデータストアに配信する、信頼の置けるトランスポート メカニズムを実現することができます。MSMQ は最終的なデータストアではありませんが、ログが記録されたデータを上で述べた最終的なストレージリポジトリに確実に配信するための信頼の置ける配信メカニズムとして使用することができます。MSMQのトランザクションとしての特性により、サーバーやネットワークが障害を起こした場合でも、ログ データが失われることはありません。
- MSMQ は、集中化されたログ記録 ソリューションが必要な場合には、選択肢として適しています。トランザクショナルな MSMQ キューを利用することで、データの損失や重複が起こらず、宛先システムに確実に配信されることを保証することができます。
- MSMQ を使用するためのコードの量は、比較的多くなります。MSMQ メッセージを変換して最終的なストアに入れるためのコードを開発する必要があります。
- MSMQ は非同期的なメカニズムなので、メッセージがその発生した順序で最終的なストアに到着するとは限りません。
- 特に MSMQ が複数のキューを通してメッセージをルーティングするように構成されている場合には、データはただちに格納されるとは限りません。これは、情報がただちに必要になるような状況では問題になることがあります。
通知
通知は、あらゆる例外管理システムの重要なコンポーネントです。ログの記録が、何が異常を来したのかを理解し、問題を修正するために何を行わなくてはならないかを把握するための機能であるのに対し、通知はその状況を最初に知らせる役割を持っています。適切な通知がなければ、そもそも例外に気づかずに終わる可能性があります。
アプリケーションが採用する通知プロセスは、アプリケーションコードから分離されなくてはなりません。通知メカニズムを変更するたびに、たとえば受信者のリストを変更するたびにコードを変更しなくてはならないというようなことがあってはなりません。アプリケーションはエラー条件を知らせることに専念し、これらのエラーの識別と適切なアクションの実行は、分離された監視システムに任せるべきです。
アプリケーションが監視システムを持たない環境内で実行される場合には、アプリケーションから通知を作成するためのオプションがいくつかあります。いずれの場合も、運用担当者か監視システムの開発者と緊密に連絡を取り合って、監視と通知の送信のための正しい手順を定義しておく必要があります。
監視アプリケーションの使用
監視システムのある環境では、いくつかの方法で通知を提供することができます。
- アプリケーションは、ログの監視と、検出されたエラー条件への対応を監視システムに任せることができます。
- 監視システムに通知を行うために、イベントを予防的に生成するコードを実装することができます。監視システムに特定の情報を予防的に通知する方法としては、Windows Management Instrumentation (WMI) が推奨されます。
ログ データ ストアの監視
ログを監視し、メッセージの到着を監視するように監視システムを構成することができます。さらに、特定のメッセージのセットのみに、あるいは特定の条件にマッチするメッセージ、たとえば定義済みのエラーレベルを超えたメッセージのみに反応するよう、モニタを微調整することもできます。Application Center Serverなどのツールは、イベント ログに記録された特定の情報を検出し、事前に構成された設定に基づいてアクションを実行することができます。
長所
- 監視システムはアプリケーション コードから分離されます。
- ログ記録の実装以外に追加のコードが不要です。
短所
- ログをポーリングし、処理の必要があるレコードをフィルタリングするよう、監視システムを構成する必要があります。
- これらのイベントを処理する他の全システムについても、選択されたログを監視するよう構成しなくてはなりません。
詳細については、Application Center Server を参照してください。
WMI イベントの作成
Windows Management Instrumentation (WMI)は、エンタープライズ環境における管理情報へのアクセスのための業界標準の、Microsoft による実装です。WMI イベントインフラストラクチャは、アプリケーションが監視と処理の可能な WMIイベントを生成できる機能を提供しています。このアプローチでは、通知の規則とプロシージャが、アプリケーション内のコードから分離されます。アプリケーションは、監視システムがキャッチできる特定のイベントのみを生成し、正しい通知プロシージャを実装するだけで済みます。.NET は System.Management および System.Management.Instrumentation 名前空間の中に、WMI プログラミングを単純化するクラスを用意しています。
すべての例外について WMI イベントを生成するべきではありません。WMIイベントは、他のシステムが反応するためのフックをアプリケーション内に作成します。つまり、WMIイベントは、外部のプロセスまたはアプリケーションによるアクションを必要とする何らかの出来事がアプリケーション内で発生したときに使用します。
長所
- 未処理例外の結果として実行される運用手順を、アプリケーション コードから分離します。
- 監視アプリケーションとの高い統合性を持っています。
- 監視アプリケーションはアプリケーション イベントを他のアプリケーション イベントとグループ化して、全社的な通知システムを提供することができます。
短所
- アプリケーションは、アプリケーションを構成する任意の数のマシンから WMIイベントを提起する可能性があります。ほとんどの場合、このようなイベントを検出するためには、監視対象のすべてのマシンに監視ツールをインストールする必要があります。このため、分散型システムでは、プロダクトのライセンシングが問題となる可能性があります。
WMI と WMI イベントの作成方法の詳細については、以下のリソースを参照してください。
通知の作成
アプリケーションで監視ツールが使用できない場合には、いくつかの方法で通知を作成することができます。一般的なオプションとしては以下のものがあります。
- SMTP を使ってメールを送信する
- カスタム通知システムを開発する
SMTP を使ってメールを送信する
通知の送信のアプローチの 1 つとして、適切なサポート要員を含んでいる配布リストにメールを送信するというものがあります。このアプローチは実装が簡単なのでよく使われますが、最も堅牢または安定した選択肢というわけではありません。
長所
- SMTP を使って短時間で簡単にソリューションを実装することができます。.NET Framework には、メールの生成と送信を単純化する System.Web.Mail 名前空間が用意されています。
短所
- このソリューションには耐障害性がありません。電子メールが正常に動作しなければ、通知は失われます。
- エラーの数が多いと、電子メールの通知が多数生成され、問題を解決するのが難しくなります。
カスタム通知システムを開発する
また、アプリケーションからメッセージを受け取り、例外の型に基づいて何らかの構成可能なアクションを実行する通知プログラムを作成することもできます。MSMQ は、データを集中化されたエラーログに送るのと同じような感覚で使用できる、データを通知システムに渡すための信頼の置ける非同期配信メカニズムを提供しています。トランザクショナルなMSMQを使うことで、データが失われたり重複したりすることなく、送信先のシステムに安定して配信されるよう保証することができます。カスタム通知アプリケーションは、監視システムの機能のサブセットのみを提供すれば済みます。
長所
- 通知プロシージャをアプリケーション コードから分離します。
- 複数のアプリケーションをサポートすることができます。
- 監視システムで生じることのあるライセンシングの問題を回避できます。
- 通知を非同期的に、信頼の置ける形で配信します。
短所
- 通知システムを開発し、保守する必要があります。
- Application Center Server などのプロダクション監視システムに、似たような機能がすでに用意されています。
テクノロジ上の具体的な注意事項
このセクションでは、一部の.NET テクノロジと組み合わせたときの、例外管理の具体的な注意事項を紹介します。
相互運用性 (Interop)
CLR は、マネージ コードとアンマネージコードを連動させるためのさまざまな相互運用性機能を提供しています。これは例外処理にも当てはまります。COMクライアントが例外を送出するマネージ クラスを呼び出すと、CLR はそれをキャッチし、アンマネージ COM コードに理解できる HRESULTに変換します。同じように、COM コンポーネントがマネージ コードに失敗を示す HRESULT を返した場合、CLR は HRESULTを例外に変換し、COM IErrorInfo インターフェイスが提供する追加情報を例外オブジェクトに組み込みます。CLR が HRESULT を認識できなかった場合には、ErrorCode プロパティを認識されない HRESULT 値に設定した総称 COMException オブジェクトを返します。
.NET Framework クラス ライブラリ (FCL) の例外の多くは、COM コンポーネントにまったく新しい HRESULT値を返します。この場合、既存の COM コードは HRESULT を理解できません。COM コードが、失敗を表す HRESULTをもとに何らかのアクションを実行している場合には、COM コードに渡す前に例外の HRESULT値を変更するようにしてください。例外オブジェクトの HResult プロパティは、アンマネージ コードに返されるHRESULT 値を表しています。これは読み書き可能なプロパティで、COMコードを正常に動作させるために任意の値に設定することができます。さらに、アンマネージ COM コードと対話を行うアプリケーション例外は、HResult プロパティをオーバーライドし、デフォルトとして例外条件を正確に表す HRESULT 値を設定するようにする必要があります。
相互運用性の詳細については、以下を参照してください。
ローカライゼーション
各オーディエンスに適切な情報を提供するためには、ローカリゼーションが必要です。アプリケーションは、オーディエンスが理解できる形式で情報が提示されるように、ローカリゼーションを提供しなくてはなりません。.NETは、アプリケーションにローカリゼーション機能を統合するために、アセンブリ カルチャーやサテライト アセンブリなどのメカニズムを用意しています。
- カルチャー
カルチャーはアセンブリ アイデンティティの一部で、バインディング プロセスに関与します。たとえば、スペイン語のオーディエンス専用のアセンブリを開発することができます。 - サテライト アセンブリ
Common Intermediate Language (CIL) コードを含んでいないリソースのみのアセンブリです。サテライト アセンブリは、アセンブリに格納されているリソースのカルチャーを正確に反映したカルチャーを指定することができます。
ローカリゼーションの詳細については、以下を参照してください。
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=237181