.NETの例外処理 Part. 3


さて、前回までの解説で、.NETアプリケーションにおける一般的な例外の取り扱い型の基本を学習してきました。キーポイントをまとめると、以下の通りです。

  • 例外は、アプリケーションエラーやシステムエラーの場合に限り、利用する。
  • 例外は、基本的にユーザアプリケーション内ではtry-catchをせず、ランタイムの持つ集約例外ハンドラの機能によって後処理する。

というわけで今回解説をするのは、ASP.NET ランタイムが持っている集約例外ハンドラの機能についてです。(基本的にはこれを学習すれば、Windows フォームなどの他のランタイムでもどのように考えればよいのかがわかると思います。)

  • 例外発生時に行うべきことは何か?-開発時と運用時
  • デフォルトエラーページの差し替え機能
  • 集約例外ハンドラによる例外ログ出力
  • XML Webサービスの場合(SOAP拡張機能による集約例外ハンドラの組み込み)
  • WCFの場合(IErrorHandlerインタフェースによる集約例外ハンドラの組み込み)
  • Exception Management Application Block
  • カスタムパブリッシャの利用

なお、以下の説明を最後まで作業した場合のサンプルを .zip 化しておきましたので、併せてお使いください。

では、以下、順番に解説していきます。

[例外発生時に行うべきことは何か?-開発時と運用時]

さて、ここまで説明してきたように、例外とはアプリケーションエラーやシステムエラーといった、業務上の想定外の事象が起きたときに発生するものでした。代表的なアプリケーションエラーやシステムエラーには、

  • データベースサーバとうまく接続できなかった。
  • メモリ不足が発生した。
  • バックエンドのシステム連携がうまくいかなかった。
  • アプリケーションが想定外の状況に陥ったので、自ら例外を発生させて自爆した。

などがありますが、こうした事象が発生した場合、例外の詳細な情報をエンドユーザに見せても(対処のしようがないので)意味がありません。このため、運用環境においては、エンドユーザに対して以下のような「穏やかな」エラー通知画面を表示することが望ましいといえるでしょう。

image

しかしながら、以下のようなことも考慮しなければなりません。

  • 開発時の場合
    例外の多くは、アプリケーションコードのバグが原因で発生しています。このため、発生した例外の中身の情報をうまく活用して、すぐさまデバッグに取り掛からなければなりません。
  • 運用時の場合
    エンドユーザに対しては上記のようなエラー通知画面を表示しますが、これに加え、バックエンドの運用担当者がすぐさまトラブルシュートを行い、システム障害を取り除く必要があります。このため、未処理例外が発生した場合には、すみやかに監視系などへの通知も行わなければなりません。

このようなことを鑑みて、ASP.NET ランタイムは以下の 3 つの例外処理機能を備えています。

  • デバッグ用エラーページ(主に開発時に利用)
    デバッグのために、未処理例外の詳細情報をその場で表示する機能
  • エラーページ差し替え機能(主に運用時に利用)
    エンドユーザに穏やかなメッセージを表示するための機能
  • 集約例外ハンドラ(開発時・運用時の両方で利用)
    未処理例外に対する後処理(ロギングなど)を作り込むことができる機能

image

では、これらの各機能について解説します。

[デバッグ用エラーページの表示機能(主に開発時に利用)]

ASP.NET ランタイムには、組み込みでデバッグ用のエラーページが含まれており、開発中に未処理例外が発生した場合には、以下のような黄色いエラーページが即時で表示されるようになっています。

image

このデフォルトのエラーページには、エラー内容やソースコード、エラー位置、スタックトレース情報などがまとめて出力されますが、このエラーページの内容は正確に理解できるようになる必要があります。なぜなら、この例外ログを正しく読めるようになると、アプリケーションバグの原因をつかみやすくなることが多いからです。例外ログの読み方については次回のエントリで詳細に解説する予定なので、まだ例外ログをうまく読むことができないという人は、必ずそちらのエントリも読むようにしてください。

[デフォルトエラーページの差し替え機能(主に運用時に利用)]

さて、上記のエラーページは開発時には非常に便利ですが、運用環境でエンドユーザがこれを見せられても、「どないせいっちゅーねん;」となって困ってしまいます。このため最初にも書いたように、以下のような「穏やかなガイダンスメッセージ」を表示するページに差し替える必要があります。

image

このようなエラーページ差し替えのためには、以下の作業を行います。

  • ガイダンスメッセージを表示する HTML ファイル
    error.htm などの名前で用意します。静的な HTML ファイルで十分で、通常はここに、エンドユーザに対するお詫び、しばらく時間をおいてから再試行をしてもらうこと、ヘルプデスクの連絡先番号などを掲載しておきます。
  • web.config ファイルへの customErrors セクションの追加
    web.config ファイルの system.web セクション下に customErrors セクションを追加し、このエラーページを指定します。

 image

   1: <customErrors mode="On" defaultRedirect="error.htm" />

以上の作業により、デフォルトの黄色いエラー画面を、適切なメッセージ通知画面に切り替えることができます。

[集約例外ハンドラへの例外ログ取得機能の組み込み(開発時・運用時の両方で利用)]

さて、エンドユーザに対するトラブルの通知メッセージについては適切なものに切り替わりましたが、これだけではまだ不十分です。というのも、依然としてシステムには何らかのトラブルが残っているわけで、発生したシステム障害については速やかに取り除き、システムを正常な状態に戻さなければなりません。

基本的な考え方としては、下図の通りとなります。

image

  • Web システムで異常事態が検知された場合、エンドユーザに対しては穏やかなメッセージを示す。
  • それと同時に、監視システムに対して何らかの方法で通知(障害が発生したことの通知)を行う。
  • 監視システムでは、トラブルが発生したことを検知したら、現地にトラブル対応を行うエンジニアを送り込む。
  • エンジニアが現地でトラブル情報の中身を見て、解決を図る。

このトラブル対応の流れで、特に注意しなければならないのは以下のポイントです。

  • このようなトラブルシュートでは、人手による対応が必要になる。

    一般に、アプリケーションエラーやシステムエラー(いわゆる例外)が発生した場合には、人手による復旧作業が必要になり、自動的にリカバリすることはできません。例えば、アプリケーション内で、データベース接続オープンに関する例外が発生した場合を考えてみます。このような例外は、① データベースサーバとの間のネットワークケーブルが断線した。② DNS サーバのトラブルによりデータベースサーバ名の名前解決ができなかった。③ データベース側にディスク障害が発生し、データベース側が接続を受け入れられなかった。④ … といった具合に、様々な原因が考えられ、これらを適切に切り分けて障害を取り除くためには、どうしても人手による対応が必要になります。
  • トラブルシュートを行う際には、発生した例外に関する情報を保存しておく必要がある。

    トラブルシュートを行うためには、発生した障害(=例外)に関する情報をどこかにきちんと記録しておき、現地に行ったエンジニアがこれを見られるようにしておかなければなりません。特に、障害の中には、同一の障害を再現させにくいものが結構あるため、「障害が発生したときにきちんと記録」しておかないと、後から「なぜその障害が起きたのか?」「どういう障害が起きたのか?」を追跡することが極端に難しくなるケースがあります。このため、発生した例外の情報は、きちんとどこかに保存しておかなければなりません。

通常、上記のような問題を解決する方法として、以下のような施策が使われます。

  • Web アプリケーションの集約例外ハンドラに、例外の情報(例外ログ)を記録する機能を組み込む。出力先はイベントログやファイルとする。
  • 監視システムでは、当該 Web システムのイベントログを監視し、イベントログに Web アプリケーションから例外ログが出力されたら、エンジニアを現地に派遣する。
  • 現地に行ったエンジニアは、例外の情報をイベントログやファイルを開いて確認し、トラブルシュートを行う。

ASP.NET ランタイムでは、上記のような施策を行えるようにするために、global.asax というファイルを用意しています。global.asax ファイルは、当該 Web アプリケーションに対してさまざまな共通機能を組み込むためのモジュールとして利用することができます。このファイル中に Application_Error() メソッドを記述しておくと、当該 Web アプリケーション内のどこで未処理例外が発生しても、必ずこのメソッドを呼び出してくれるようになります。(つまり、ここ一か所に例外ログの出力機能を書いておけば、アプリケーション全体に対して未処理例外の処理機能が有効化される、ということになります。このため、この機能を集約例外ハンドラと呼んでいます。)

image

集約例外ハンドラの実装例を示します。

   1: <%@ Application Language="C#" %>
   2: <%@ Import Namespace="System" %>
   3: <%@ Import Namespace="System.Diagnostics" %>
   4:  
   5: <script runat="server">
   6:     void Application_Error(object sender, EventArgs e)
   7:     {
   8:         Exception ex = Server.GetLastError(); // 未処理例外を取り出してイベントログに出力
   9:         string errMsg = String.Format("エラーメッセージ\n{0}\n\nスタックトレース\n{1}\n",
  10:                             ex.Message, ex.StackTrace);
  11:         EventLog.WriteEntry("Application", errMsg, EventLogEntryType.Error);
  12:     }
  13: </script>

このような機能を作り込んだ上で、Web アプリケーションを実行し、アプリケーションで未処理例外が発生すると、この集約例外ハンドラ機能が動作し、イベントログにエラーログが出力されます。

image

image

イベントログには以下のようなメッセージが出力されるようになります。(内容については現時点ではわからなくて OK です。これについては次回のエントリで解説します。)

ソース "Application" からのイベント ID 0 の説明が見つかりません。このイベントを発生させるコンポーネントがローカル コンピューターにインストールされていないか、インストールが壊れています。ローカル コンピューターにコンポーネントをインストールするか、コンポーネントを修復してください。
 
イベントが別のコンピューターから発生している場合、イベントと共に表示情報を保存する必要があります。
 
イベントには次の情報が含まれています: 
 
エラーメッセージ
種類 'System.Web.HttpUnhandledException' の例外がスローされました。
 
スタックトレース
   場所 System.Web.UI.Page.HandleError(Exception e)
   場所 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   場所 System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   場所 System.Web.UI.Page.ProcessRequest()
   場所 System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
   場所 System.Web.UI.Page.ProcessRequest(HttpContext context)
   場所 ASP.default_aspx.ProcessRequest(HttpContext context)
   場所 System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   場所 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
 
 
メッセージ リソースは存在しますが、メッセージが文字列テーブル/メッセージ テーブルに見つかりません。

[ここまでのまとめ]

では、ここまでの解説の要点をまとめておきます。

  • 開発時には、例外の多くは、アプリケーションコードのバグが原因で発生しています。このため、発生した例外の中身の情報をうまく活用して、すぐさまデバッグに取り掛からなければなりません。このため、ASP.NET 組み込みのデフォルトエラーページを活用して、デバッグを行います。
  • 運用時には、エンドユーザに対しては穏やかなエラーメッセージを表示しなければなりません。このために、エラーページの差し替え機能を利用します。しかし、これに加え、バックエンドの運用担当者がすぐさまトラブルに気づき、トラブルシュートを行い、システム障害を取り除く必要があります。このために、集約例外ハンドラ機能を利用し、例外情報をイベントログなどに記録します。

さて、ASP.NET において最も基本となる例外処理の考え方は以上なのですが、もう少し追加で知っておくべきポイントがあります。具体的には以下の 2 つです。

  • 集約例外ハンドラの組み込み方法

    XML Web サービスや WCF では、集約例外ハンドラの組み込み方が異なりますので、これらについても理解しておく必要があります。
  • 詳細な例外ログの取得方法

    上記の方法では、簡単な例外ログしか取得できませんが、トラブルシュートのやりやすさを考えると、できる限り詳細かつ分かりやすい例外ログを出力しておく必要があります。

これらについても解説しましょう。

[XML Web サービス(.asmx)における集約例外ハンドラの組み込み方法]

XML Web サービス(*.asmx ファイル)を使う場合には、global.asax ファイルの Application_Error() 関数を使った集約例外ハンドラの作り込みはできません。これは理由は簡単で、SOAP over HTTP の XML Web サービス呼び出しを行う場合、*.asmx ファイル内で発生した未処理例外は、ASP.NET ランタイムがこれを捕捉し、SOAP Fault 要素と呼ばれる SOAP メッセージに変換してクライアントに通知するためです。つまり、サーバの *.asmx ファイル内で未処理例外が発生しても、HTTP 通信としてはステータス 200 の正常通信扱いとなるため、global.asax ファイルの Application_Error() 関数は動作しません。

image

このため、*.asmx に対する集約例外ハンドラを作りたい場合には、SOAP 拡張と呼ばれる機能を利用し、ASP.NET ランタイム内での SOAP メッセージ組立処理のところをカスタマイズしてやります。具体的には以下のような作業を行います。

  • ソリューションファイルに、クラスライブラリプロジェクトを追加し、そこに SOAP 拡張クラスを作成。
  • SOAP 拡張クラスを使って、未処理例外があった場合にはイベントログに記録するコードを記述。
  • Web サイト側からプロジェクト参照を行った上で、web.config ファイルに SOAP 拡張クラスの組み込みを指示するセクションを記述。

image

(SOAP 拡張クラスの実装)

   1: using System;
   2: using System.Web.Services.Protocols;
   3: using System.Diagnostics;
   4: using System.Reflection;
   5:  
   6: namespace Microsoft.Japan.Mcs.Utilities.ExceptionManagement.SoapWebService
   7: {
   8:     public class ExceptionLoggingExtension : SoapExtension
   9:     {
  10:         public override object GetInitializer(Type WebServiceType)
  11:         {
  12:             return null;
  13:         }
  14:  
  15:         public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
  16:         {
  17:             return null;
  18:         }
  19:  
  20:         public override void Initialize(object initializer)
  21:         {
  22:         }
  23:  
  24:         public override void ProcessMessage(SoapMessage message)
  25:         {
  26:             switch (message.Stage)
  27:             {
  28:                 case SoapMessageStage.BeforeDeserialize:
  29:                     break;
  30:                 case SoapMessageStage.AfterDeserialize:
  31:                     break;
  32:                 case SoapMessageStage.BeforeSerialize:
  33:                     break;
  34:                 case SoapMessageStage.AfterSerialize:
  35:                     if (message.Exception != null)
  36:                     {
  37:                         Exception ex = message.Exception;
  38:                         string warningMessage = "集約エラーハンドラにて例外を補足しました。例外内容は以下の通りです。" + ex.ToString();
  39:                         EventLog el = new EventLog();
  40:                         el.Source = "Application";
  41:                         el.WriteEntry(warningMessage, EventLogEntryType.Error);
  42:                     }
  43:                     break;
  44:                 default:
  45:                     throw new ApplicationException();
  46:             }
  47:         }
  48:     }
  49: }
  50:  
  51:  

(web.config ファイルへの修正) (※ type の指定は、”クラス名, DLL ファイル名” の形式)

   1: <webServices>
   2:   <soapExtensionTypes>
   3:     <add type="Microsoft.Japan.Mcs.Utilities.ExceptionManagement.SoapWebService.ExceptionLoggingExtension, Microsoft.Japan.Mcs.Utilities" />
   4:   </soapExtensionTypes>
   5: </webServices>

以上により、XML Web サービスに対して集約例外ハンドラが組み込まれます。なお、この集約例外ハンドラは SOAP 拡張を使っているため、ブラウザからの HTTP-POST による呼び出しに対しては機能しません。このため、上記の集約例外ハンドラの動作を検証したい場合には、テスト用のコンソールアプリケーションなどを作ってテストを行っていただければと思います。

[WCF (*.svc ファイル)における集約例外ハンドラの組み込み方法]

WCF の場合も、前述した *.asmx の場合と同様に、ASP.NET ランタイムの集約例外ハンドラは動作しません。このため、WCF ランタイムの中に含まれる集約例外ハンドラ機能を使って、例外ハンドラを作り込む必要があります。

WCF の場合には、集約例外ハンドラを作り込むための機能として、IErrorHandler というインタフェースが存在します。このインタフェースを継承したクラスを作成したのち、これをサービスホストのディスパッチャに組み込みます。具体的なコードは以下の通りです。(ちょっと長いですが;)

   1: <%@ ServiceHost Language="C#" Debug="true" Factory="WcfServiceHostFactory" %>
   2:  
   3: using System;
   4: using System.Diagnostics;
   5: using System.ServiceModel;
   6: using System.ServiceModel.Activation;
   7: using System.ServiceModel.Description;
   8: using System.ServiceModel.Dispatcher;
   9: using System.ServiceModel.Channels;
  10:  
  11: public class WcfServiceHostFactory : ServiceHostFactory
  12: {
  13:     public override ServiceHostBase CreateServiceHost(string service, Uri[] baseAddresses)
  14:     {
  15:         ServiceHost host = new ServiceHost(typeof(WcfService), baseAddresses);
  16:         host.AddServiceEndpoint(typeof(WcfService), new BasicHttpBinding(), "");
  17:         ServiceMetadataBehavior svb1 = new ServiceMetadataBehavior();
  18:         svb1.HttpGetEnabled = true;
  19:         host.Description.Behaviors.Add(svb1);
  20:         // 集約例外ハンドラの組み込み
  21:         host.Description.Behaviors.Add(new GlobalErrorHandlerBehavior());
  22:         return host;
  23:     }
  24: }
  25:  
  26: [ServiceContract]
  27: public class WcfService
  28: {
  29:     [OperationContract]
  30:     public string GetMessage(string name)
  31:     {
  32:         return "Hello World, " + name;
  33:     }
  34:  
  35:     [OperationContract]
  36:     public int CountAuthorsByState(string state)
  37:     {
  38:         PubsDataSetTableAdapters.QueriesTableAdapter ta = new PubsDataSetTableAdapters.QueriesTableAdapter();
  39:         int count = ta.CountAuthorsByState(state).Value;
  40:         return count;
  41:     }
  42: }
  43:  
  44: public class GlobalErrorHandler : IErrorHandler
  45: {
  46:     public bool HandleError(Exception error)
  47:     {
  48:         Exception ex = error;
  49:         string warningMessage = "集約エラーハンドラにて例外を補足しました。例外内容は以下の通りです。" + ex.ToString();
  50:         EventLog el = new EventLog();
  51:         el.Source = "Application";
  52:         el.WriteEntry(warningMessage, EventLogEntryType.Error);
  53:         return false;
  54:     }
  55:  
  56:     public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
  57:     {
  58:         return;
  59:     }
  60: }
  61:  
  62: public class GlobalErrorHandlerBehavior : IServiceBehavior
  63: {
  64:     public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
  65:     {
  66:     }
  67:  
  68:     public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  69:     {
  70:         foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
  71:         {
  72:             ChannelDispatcher cd = cdb as ChannelDispatcher;
  73:             if (cd != null) cd.ErrorHandlers.Add(new GlobalErrorHandler());
  74:         }
  75:     }
  76:  
  77:     public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  78:     {
  79:     }
  80: }

※ 上記のコードについてはここではこれ以上深入りしませんが、興味がある方は *.svc ファイルに関するこちらの説明も読んでみていただけるとよいかと思います。

[Exception Management Application Block の利用]

さて、ここまでの説明で、サーバ側のアプリケーションに集約例外ハンドラを仕掛けることはできるようになりました。しかし、現在の方法だけではまだ十分な例外情報が出力されているとは言えません。集約例外ハンドラ内でどれだけ詳細なログを取得できるかによって、運用系で発生した障害の解析容易性が大きく変わりますが、特に偶発的に発生するような例外の場合には、どのようなシチュエーションで例外が発生したのかを詳細にロギングしておかないと、解析だけでなく再現なども困難になります。具体的には、以下のようなデータも例外と合わせて記録しておくことが望まれます。

image

こうした詳細な例外ログを取得するには、patterns & practices が提供している Exception Management Application Block (EMAB)を使うのが便利です。EMAB は .NET Framework 1.0 が提供され始めた 2002 年頃に、patterns & practices というマイクロソフトの開発のベストプラクティスを整備するチームが開発・提供したもの。現在はより広範な機能を含んだ Enterprise Library と呼ばれるライブラリに拡張されているのですが、こちらは重量級のライブラリとなってしまっているという難点もあります。単純に例外ロギング機能のみを使いたい、という場合には、単体導入が可能な小さなライブラリで、他に影響を与えにくいライブラリの方が便利なこともあり、この目的には Enterprise Library よりもむしろ EMAB の方が合致していることもあります。このため、今回はこちらを使うことにしましょう。

EMAB は、以下の 4 つのステップにより導入することができます。

  • ① イベントソースの登録

    イベントログに例外情報を出力できるようにするために、事前にアプリケーション名をレジストリに登録する。
  • ② EMAB のモジュールのダウンロードとソリューションへの組み込み

    MSDN Webサイトからソースコードをダウンロードし、各自のユーザアプリケーション(ソリューション)へと追加する。
  • ③ 構成ファイルの設定

    web.config ファイルに設定情報を追加し、EMAB を有効化する。
  • ④ global.asax の集約エラーハンドラへのコードの追

    集約エラーハンドラにて、(自力でイベントログに出力するかわりに)EMABを呼び出す。

以下に順番に解説していきましょう。

① イベントソースの登録

Windows のイベントログには、ログの名称(ログのカテゴリ)と、イベントソース名という二つの重要な概念があります。

image

イベントログにデータを書き出す際には、どのカテゴリに、どのようなソース名で出力するかが重要になります。中でもイベントソース名は非常に重要で、監視システムからアプリケーション障害を監視する際、このイベントソース名を見て、特定アプリケーションの障害有無を確認するのが基本となるため、実際のシステムでは、汎用名称である “Application” ではなく、適切な名称でイベントログを出力しなければなりません。

さて、イベントソース名には、通常、当該アプリケーションの名称が利用されますが、このイベントソース名は事前にレジストリにデータ登録しておく必要があります。イベントソース名が事前登録されていなかった場合には、EventLog クラスはイベントソース名を動的にレジストリ登録するよう試みますが、この処理には管理者権限が必要なため、

  • 開発環境の場合

    デスクトップログオンユーザ(多くの場合には管理者権限を持つ)が Web サーバ(ASP.NET 開発サーバ)を動作させているため、動的にイベントソース名が登録される。結果として、事前のイベントソース名の登録は不要。
  • 運用環境の場合

    ネットワークサービスと呼ばれる権限の低いアカウントで Web サーバのワーカプロセス(w3wp.exe)が動作している。このため、事前にイベントソース名を登録しておかなければならない

となります。イベントソースの登録には、以下のようなコンソールアプリケーションを作成し、管理者権限で動作させるのが最も簡単です。

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Diagnostics;
   6:  
   7: namespace ConsoleApplication1
   8: {
   9:     class Program
  10:     {
  11:         static void Main(string[] args)
  12:         {
  13:             if (EventLog.SourceExists("McsSampleApp") == false)
  14:             {
  15:                 EventLog.CreateEventSource("McsSampleApp", "Application");
  16:                 Console.WriteLine("イベントソースを作成しました。");
  17:             }
  18:             else
  19:             {
  20:                 Console.WriteLine("イベントソースはすでに存在します。");
  21:             }
  22:         }
  23:     }
  24: }

② EMAB のモジュールのダウンロードとソリューションへの組み込み

次に、MSDN Web サイトから EMAB のパッケージをダウンロードして展開したのち、ソースコードとして含まれている以下の 2 つのプロジェクトを取り出し、自分のソリューションに追加します。

  • Microsoft.ApplicationBlocks.ExceptionManagement
  • Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces

image

なおこの EMAB は .NET 1.0 用のソースコードであるため、VS2008 のファイル形式へのコンバートが要求されます。またソースコードの一部に、.NET Framework 2.0 では古い API としてマークされているものが含まれており、コンパイル時に警告が出ます。そのまま利用しても問題ありませんが、直したいという方はソースコードを修正して利用してください。

③ 構成ファイルの設定

次に、Web サイトからこの二つのプロジェクトに対してプロジェクト参照設定を行い、web.config ファイルを修正します。具体的には、configSections への section タグの追加と、exceptionManagement セクションの追加を行います。

image

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="exceptionManagement" type="Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManagerSectionHandler, Microsoft.ApplicationBlocks.ExceptionManagement" />
   5:     <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
   6:       ... (中略) ...
   7:     </sectionGroup>
   8:   </configSections>
   9:  
  10:   <exceptionManagement>
  11:     <publisher assembly="Microsoft.ApplicationBlocks.ExceptionManagement"
  12:                    type="Microsoft.ApplicationBlocks.ExceptionManagement.DefaultPublisher"
  13:                    logname="Application"
  14:                    applicationname="McsSampleApp" />
  15:   </exceptionManagement>
  16:  
  17:   ... (後略) ...

<exceptionManagement> タグの中で、イベントログの出力先となるログ名称とアプリケーション名称を設定していることに着目してください。必要に応じてこの二つを書き換えます。

④ global.asax の集約エラーハンドラへのコードの追加

集約エラーハンドラにて、(自力でイベントログに出力するかわりに)EMAB を呼び出し、EMAB に例外情報を出力してもらうようにします。

   1: <%@ Application Language="C#" %>
   2: <%@ Import Namespace="System" %>
   3: <%@ Import Namespace="System.Diagnostics" %>
   4:  
   5: <script runat="server">
   6:     void Application_Error(object sender, EventArgs e)
   7:     {
   8:         Exception ex = Server.GetLastError();
   9:         // 404 Not Foundは除外する
  10:         if (ex is HttpException)
  11:         {
  12:             if (((HttpException)ex).GetHttpCode() == 404)
  13:                 return;
  14:         }
  15:         Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManager.Publish(ex);
  16:     }
  17: </script>

※ なお、出力は ExceptionManager.Publish() メソッドで行うことになりますが、実際に EMAB をアプリケーションに組み込む場合には、上記のコードのように 404 Not Found エラーを避けるようなコードを追加することをお勧めします。(404 Not Found エラーが発生した際にいちいち監視系に通知がいくと非常に面倒なため)

以上で EMAB の組み込みは完了です。EMAB を組み込んでおくと、イベントログに以下のような「綺麗なフォーマッティングがなされた例外ログ」が出力されるようになります。

image

   1:  
   2: General Information 
   3: *********************************************
   4: Additional Info:
   5: ExceptionManager.MachineName: NAKAMA07
   6: ExceptionManager.TimeStamp: 2009/01/18 18:48:48
   7: ExceptionManager.FullName: Microsoft.ApplicationBlocks.ExceptionManagement, Version=1.0.3305.33708, Culture=neutral, PublicKeyToken=null
   8: ExceptionManager.AppDomainName: 670e817-12-128767457240733673
   9: ExceptionManager.ThreadIdentity: FAREAST\nakama
  10: ExceptionManager.WindowsIdentity: FAREAST\nakama
  11:  
  12: 1) Exception Information
  13: *********************************************
  14: Exception Type: System.Web.HttpUnhandledException
  15: ErrorCode: -2147467259
  16: Message: 種類 'System.Web.HttpUnhandledException' の例外がスローされました。
  17: Data: System.Collections.ListDictionaryInternal
  18: TargetSite: Boolean HandleError(System.Exception)
  19: HelpLink: NULL
  20: Source: System.Web
  21:  
  22: StackTrace Information
  23: *********************************************
  24:    場所 System.Web.UI.Page.HandleError(Exception e)
  25:    場所 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
  26:    場所 System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
  27:    場所 System.Web.UI.Page.ProcessRequest()
  28:    場所 System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
  29:    場所 System.Web.UI.Page.ProcessRequest(HttpContext context)
  30:    場所 ASP.default_aspx.ProcessRequest(HttpContext context)
  31:    場所 System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
  32:    場所 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
  33:  
  34: 2) Exception Information
  35: *********************************************
  36: Exception Type: System.Data.SqlClient.SqlException
  37: Errors: System.Data.SqlClient.SqlErrorCollection
  38: Class: 14
  39: LineNumber: 65536
  40: Number: 15350
  41: Procedure: 
  42: Server: \\.\pipe\CA14CFCB-F631-49\tsql\query
  43: State: 1
  44: Source: .Net SqlClient Data Provider
  45: ErrorCode: -2146232060
  46: Message: ファイル C:\Users\nakama\Documents\Visual Studio 2008\Projects\WebSite1\WebSite1\App_Data\pubs.mdf の自動的に名前が付けられたデータベースをアタッチできませんでした。同じ名前のデータベースが既に存在するか、指定されたファイルを開けないか、UNC 共有に配置されています。
  47: Data: System.Collections.ListDictionaryInternal
  48: TargetSite: Void OnError(System.Data.SqlClient.SqlException, Boolean)
  49: HelpLink: NULL
  50:  
  51: StackTrace Information
  52: *********************************************
  53:    場所 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
  54:    場所 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
  55:    場所 System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
  56:    場所 System.Data.SqlClient.SqlInternalConnectionTds.CompleteLogin(Boolean enlistOK)
  57:    場所 System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, Boolean ignoreSniOpenTimeout, Int64 timerExpire, SqlConnection owningObject)
  58:    場所 System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(String host, String newPassword, Boolean redirectedUserInstance, SqlConnection owningObject, SqlConnectionString connectionOptions, Int64 timerStart)
  59:    場所 System.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(SqlConnection owningObject, SqlConnectionString connectionOptions, String newPassword, Boolean redirectedUserInstance)
  60:    場所 System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, Object providerInfo, String newPassword, SqlConnection owningObject, Boolean redirectedUserInstance)
  61:    場所 System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection)
  62:    場所 System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnection owningConnection, DbConnectionPool pool, DbConnectionOptions options)
  63:    場所 System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject)
  64:    場所 System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject)
  65:    場所 System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
  66:    場所 System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
  67:    場所 System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
  68:    場所 System.Data.SqlClient.SqlConnection.Open()
  69:    場所 PubsDataSetTableAdapters.QueriesTableAdapter.CountAuthorsByState(String state)
  70:    場所 _Default.Button1_Click(Object sender, EventArgs e)
  71:    場所 System.Web.UI.WebControls.Button.OnClick(EventArgs e)
  72:    場所 System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
  73:    場所 System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
  74:    場所 System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
  75:    場所 System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData)
  76:    場所 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

なお、実際にはこの例外ログをきちんと読めるようになることが極めて重要です。例外ログの読み方については、次回のエントリで解説します。

[カスタムパブリッシャの利用]

さて、前述の方法で EMAB による例外ログがイベントログに出力されるようになりました。しかし、標準の EMAB は、Web アプリケーション固有の情報を出力してくれるようにはなっておらず、Web アプリケーションの障害解析時としては十分なデータを得ることができません。

幸い、EMAB には、カスタムパブリッシャと呼ばれるものを追加開発することにより、機能拡張や多彩な例外出力を実現できるようになっています。この機能を使って、Web アプリケーション関連の情報を取り出して出力するようにすれば、障害解析に役立つデータをよりたくさん得ることができるようになります。

image

具体的には以下の作業を行います。

  • カスタムパブリッシャクラスの作成
  • web.config ファイルへの修正

ここでは細かいコードなどについては解説しませんので、興味がある方はコードをじっくり読んでみてください。

image

   1: using System;
   2: using System.Xml;
   3: using System.Xml.Serialization;
   4: using System.Text;
   5: using System.IO;
   6: using System.Collections;
   7: using System.Collections.Specialized;
   8: using System.Web;
   9: using System.Web.SessionState;
  10: using System.Diagnostics;
  11: using System.Reflection;
  12: using System.Runtime.Serialization.Formatters.Soap;
  13: using Microsoft.ApplicationBlocks.ExceptionManagement;
  14:  
  15: namespace Microsoft.Japan.Mcs.Utilities.ExceptionManagement
  16: {
  17:     /// <summary>
  18:     /// Microsoft Exception Management Application Blockに取り込んで利用する、
  19:     /// カスタムの例外パブリッシャ。HTTP関連情報と共に、ローカルのログファイルへと
  20:     /// 出力する。ローカルログファイル名はweb.config内にてfilename属性により指定、
  21:     /// 無指定の場合には自動的に名前がつけられ、Cドライブ直下に保存される。
  22:     /// </summary>
  23:     public class TraceLogFilePublisher : IExceptionPublisher
  24:     {
  25:  
  26:         /// <summary>
  27:         /// デフォルトコンストラクタ。ExceptionManagerが利用する。
  28:         /// </summary>
  29:         public TraceLogFilePublisher()
  30:         {
  31:         }
  32:  
  33:         /// <summary>
  34:         /// 例外情報をHTTPの各種の情報と共にログファイルへ出力します。
  35:         /// </summary>
  36:         /// <param name="exception">発生した例外情報</param>
  37:         /// <param name="additionalInfo">追加情報</param>
  38:         /// <param name="configSettings">構成情報</param>
  39:         public void Publish(Exception exception, NameValueCollection additionalInfo, NameValueCollection configSettings)
  40:         {
  41:             StringBuilder strInfo = new StringBuilder();
  42:             strInfo.Append("\n\n***** TraceLogFilePublisher からのエラー情報 *****\n");
  43:  
  44:             #region DefaultPublisher でも出力している一般的なエラー情報(DefaultPublisherクラスから引用)
  45:  
  46:             #region Geberal Exception Information
  47:             // DefaultPublisher Information
  48:  
  49:             if (additionalInfo != null)
  50:             {
  51:                 // Record General information.
  52:                 strInfo.AppendFormat("\n" + "[General Information]");
  53:  
  54:                 foreach (string i in additionalInfo)
  55:                 {
  56:                     strInfo.AppendFormat("\n{0}: {1}", i, additionalInfo.Get(i));
  57:                 }
  58:             }
  59:             #endregion
  60:  
  61:             if (exception == null)
  62:             {
  63:                 strInfo.Append("\n\nNo Exception.\n");
  64:             }
  65:             else
  66:             {
  67:                 #region Loop through each exception class in the chain of exception objects
  68:                 // Loop through each exception class in the chain of exception objects.
  69:                 Exception currentException = exception; // Temp variable to hold InnerException object during the loop.
  70:                 int intExceptionCount = 1;              // Count variable to track the number of exceptions in the chain.
  71:                 do
  72:                 {
  73:                     // Write title information for the exception object.
  74:                     strInfo.AppendFormat("\n\n{0}) Exception Information\n{1}", intExceptionCount.ToString(), "");
  75:                     strInfo.AppendFormat("\nException Type: {0}", currentException.GetType().FullName);
  76:  
  77:                     #region Loop through the public properties of the exception object and record their value
  78:                     // Loop through the public properties of the exception object and record their value.
  79:                     PropertyInfo[] aryPublicProperties = currentException.GetType().GetProperties();
  80:                     NameValueCollection currentAdditionalInfo;
  81:                     foreach (PropertyInfo p in aryPublicProperties)
  82:                     {
  83:                         // Do not log information for the InnerException or StackTrace. This information is
  84:                         // captured later in the process.
  85:                         if (p.Name != "InnerException" && p.Name != "StackTrace")
  86:                         {
  87:                             if (p.GetValue(currentException, null) == null)
  88:                             {
  89:                                 strInfo.AppendFormat("\n{0}: NULL", p.Name);
  90:                             }
  91:                             else
  92:                             {
  93:                                 // Loop through the collection of AdditionalInformation if the exception type is a BaseApplicationException.
  94:                                 if (p.Name == "AdditionalInformation" && currentException is BaseApplicationException)
  95:                                 {
  96:                                     // Verify the collection is not null.
  97:                                     if (p.GetValue(currentException, null) != null)
  98:                                     {
  99:                                         // Cast the collection into a local variable.
 100:                                         currentAdditionalInfo = (NameValueCollection)p.GetValue(currentException, null);
 101:  
 102:                                         // Check if the collection contains values.
 103:                                         if (currentAdditionalInfo.Count > 0)
 104:                                         {
 105:                                             strInfo.Append("\nAdditionalInformation:");
 106:  
 107:                                             // Loop through the collection adding the information to the string builder.
 108:                                             for (int i = 0; i < currentAdditionalInfo.Count; i++)
 109:                                             {
 110:                                                 strInfo.AppendFormat("\n{0}: {1}", currentAdditionalInfo.GetKey(i), currentAdditionalInfo[i]);
 111:                                             }
 112:                                         }
 113:                                     }
 114:                                 }
 115:                                 // Otherwise just right the ToString() value of the property.
 116:                                 else
 117:                                 {
 118:                                     strInfo.AppendFormat("\n{0}: {1}", p.Name, p.GetValue(currentException, null));
 119:                                 }
 120:                             }
 121:                         }
 122:                     }
 123:                     #endregion
 124:                     #region Record the Exception StackTrace
 125:                     // Record the StackTrace with separate label.
 126:                     if (currentException.StackTrace != null)
 127:                     {
 128:                         strInfo.AppendFormat("\n" + "StackTrace Information" + "\n");
 129:                         strInfo.AppendFormat("\n{0}", currentException.StackTrace);
 130:                     }
 131:                     #endregion
 132:  
 133:                     // Reset the temp exception object and iterate the counter.
 134:                     currentException = currentException.InnerException;
 135:                     intExceptionCount++;
 136:                 } while (currentException != null);
 137:                 #endregion
 138:             }
 139:  
 140:             #endregion
 141:  
 142:             #region Web システム固有の情報を取得・出力するルーチン
 143:  
 144:             if (HttpContext.Current == null)
 145:             {
 146:                 strInfo.Append("\n\n[HTTP Information]\nNo Information.\n");
 147:             }
 148:             else
 149:             {
 150:                 strInfo.Append("\n\n[HTTP Information]\n");
 151:  
 152:                 HttpRequest Request = HttpContext.Current.Request;
 153:  
 154:                 if (Request != null)
 155:                 {
 156:                     // HTTP サーバ環境変数の出力
 157:                     strInfo.Append("[HTTP ServerVariables (HTTPサーバ環境変数)]\n");
 158:                     foreach (string key in Request.ServerVariables.AllKeys)
 159:                     {
 160:                         strInfo.Append(String.Format("{0} : {1}\n", key, Request.ServerVariables[key]));
 161:                     }
 162:                     strInfo.Append("\n");
 163:  
 164:                     //HTTP リクエストヘッダの出力
 165:                     strInfo.Append("[HTTP Request Headers (HTTPリクエストヘッダ)]\n");
 166:                     foreach (string key in Request.Headers.AllKeys)
 167:                     {
 168:                         strInfo.Append(String.Format("{0} : {1}\n", key, Request.Headers[key]));
 169:                     }
 170:                     strInfo.Append("\n");
 171:  
 172:                     //HTTP リクエストクッキー情報の出力
 173:                     strInfo.Append("[HTTP Request Cookies (HTTPリクエストクッキー)]\n");
 174:                     foreach (string key in Request.Cookies.AllKeys)
 175:                     {
 176:                         strInfo.Append(String.Format("{0} : {1}\n", key, Request.Cookies[key].Value));
 177:                     }
 178:                     strInfo.Append("\n");
 179:  
 180:                     //HTTP クエリ文字列の出力
 181:                     strInfo.Append("[HTTP Request QueryString (HTTPクエリ文字列)]\n");
 182:                     foreach (string key in Request.QueryString.AllKeys)
 183:                     {
 184:                         strInfo.Append(String.Format("{0} : {1}\n", key, Request.QueryString[key]));
 185:                     }
 186:                     strInfo.Append("\n");
 187:  
 188:                     //HTTP フォームデータの出力
 189:                     strInfo.Append("[HTTP Request Form (HTTPフォーム)]\n");
 190:                     foreach (string key in Request.Form.AllKeys)
 191:                     {
 192:                         strInfo.Append(String.Format("{0} : {1}\n", key, Request.Form[key]));
 193:                     }
 194:                     strInfo.Append("\n");
 195:                 }
 196:                 else
 197:                 {
 198:                     strInfo.Append("[HTTP Request Information]\nデータがありません。\n\n");
 199:                 }
 200:  
 201:                 HttpResponse Response = HttpContext.Current.Response;
 202:  
 203:                 if (Response != null)
 204:                 {
 205:                     // HTTP レスポンスステータス情報の出力
 206:                     strInfo.Append("[HTTP Response Status (HTTPレスポンス状態)]\n");
 207:                     strInfo.Append(String.Format("Status : {0}\n", Response.Status));
 208:                     strInfo.Append(String.Format("Status Code : {0}\n", Response.StatusCode));
 209:                     strInfo.Append("\n");
 210:  
 211:                     // HTTP レスポンスクッキー情報の出力
 212:                     strInfo.Append("[HTTP Response Cookies (HTTPレスポンスクッキー)]\n");
 213:                     foreach (string key in Response.Cookies.AllKeys)
 214:                     {
 215:                         strInfo.Append(String.Format("{0} : {1}\n", key, Response.Cookies[key].Value));
 216:                     }
 217:                     strInfo.Append("\n");
 218:                 }
 219:                 else
 220:                 {
 221:                     strInfo.Append("[HTTP Response Information]\nデータがありません。\n\n");
 222:                 }
 223:  
 224:                 HttpSessionState Session = HttpContext.Current.Session;
 225:  
 226:                 if (Session != null)
 227:                 {
 228:                     // HTTP Session内に含まれている情報の出力
 229:                     strInfo.Append("[HTTP Session Object (Sessionオブジェクト内の情報)]\n");
 230:  
 231:                     foreach (string key in Session.Keys)
 232:                     {
 233:                         // Session内には任意のオブジェクトが格納されるが、その中身をテキスト化するには
 234:                         // ToString()では不十分。XmlSerializerクラスを利用して、XMLテキスト化する。
 235:                         object obj = Session[key];
 236:                         string xml = null;
 237:                         try
 238:                         {
 239:                             TextWriter writer = new StringWriter();
 240:                             XmlSerializer serializer = new XmlSerializer(obj.GetType());
 241:                             serializer.Serialize(writer, obj);
 242:                             xml = writer.ToString();
 243:                         }
 244:                         catch (Exception)
 245:                         {
 246:                             MemoryStream memstream = new MemoryStream();
 247:                             SoapFormatter formatter = new SoapFormatter();
 248:                             formatter.Serialize(memstream, obj);
 249:                             StreamReader reader = new StreamReader(memstream);
 250:                             reader.BaseStream.Seek(0, SeekOrigin.Begin);
 251:                             xml = reader.ReadToEnd();
 252:                         }
 253:                         strInfo.Append(String.Format("{0}({1}) : \n{2}\n", key, obj.GetType().Name, xml));
 254:                     }
 255:                     strInfo.Append("\n");
 256:                 }
 257:                 else
 258:                 {
 259:                     strInfo.Append("[HTTP Session Information]\nデータがありません。\n\n");
 260:                 }
 261:             }
 262:  
 263:             #endregion
 264:  
 265:             #region ローカルログファイルへの出力処理
 266:  
 267:             #region web.config からの出力ファイル名の取得
 268:  
 269:             string filename = null;
 270:  
 271:             if (configSettings["fileName"] != null &&
 272:                 configSettings["fileName"].Length > 0)
 273:             {
 274:                 filename = configSettings["fileName"];
 275:             }
 276:             else
 277:             {
 278:                 // ファイル名の指定がない場合には、アプリケーションドメイン名を利用したファイル名で出力する
 279:                 string applicationName = AppDomain.CurrentDomain.FriendlyName;
 280:                 applicationName = applicationName.Replace("/", "_");
 281:                 filename = @"C:\" + applicationName + "_tracelog.txt";
 282:             }
 283:             #endregion
 284:  
 285:             using (System.Threading.Mutex m = new System.Threading.Mutex(false, this.GetType().FullName + ".FileWrite"))
 286:             {
 287:                 m.WaitOne();
 288:  
 289:                 // 既存ファイルがある場合はアペンド扱い
 290:                 using (StreamWriter sw = new StreamWriter(filename, true, System.Text.Encoding.Default))
 291:                 {
 292:                     sw.Write(strInfo.ToString());
 293:                     sw.Close();
 294:                 }
 295:  
 296:                 m.ReleaseMutex();
 297:             }
 298:             #endregion
 299:  
 300:         }
 301:     }
 302: }

   1: <exceptionManagement>
   2:   <publisher assembly="Microsoft.ApplicationBlocks.ExceptionManagement"
   3:                  type="Microsoft.ApplicationBlocks.ExceptionManagement.DefaultPublisher"
   4:                  logname="Application"
   5:                  applicationname="McsSampleApp" />
   6:   <!-- ※ ファイル出力先の設定に注意(アクセス権があるフォルダを選ぶこと) -->
   7:   <publisher assembly="Microsoft.Japan.Mcs.Utilities"
   8:                  type="Microsoft.Japan.Mcs.Utilities.ExceptionManagement.TraceLogFilePublisher"
   9:          fileName="C:\Log\WebSite1_EMAB.log" />
  10: </exceptionManagement>

[本エントリのまとめ]

というわけで、本エントリでは ASP.NET を中心として、開発時や運用時にどんな例外の後処理を行わなければならないのかについて解説を進めてきました。キーポイントをまとめると、以下の通りになります。

  • 開発時には、発生した例外の中身の情報をうまく活用して、すぐさまデバッグに取り掛かる。このために、ASP.NET 組み込みのデフォルトエラーページを活用する。
  • 運用時には、エンドユーザに対しては穏やかなエラーメッセージを表示する。このために、ASP.NET のエラーページの差し替え機能を利用する。
  • さらに運用時には、例外発生時(=障害発生時)に、バックエンドの運用担当者がすぐに障害に気づけるようにしなければならない。このために、集約例外ハンドラ機能を利用し、例外情報をイベントログなどに記録する。
  • 集約例外ハンドラを使って記録する例外情報(例外ログ)は、 システムの障害を取り除くための重要な手がかりとなる。このため、集約例外ハンドラでは、可能な限り詳細な例外ログを取得することが望ましい。
  • 上記の目的のためには、EMAB (Exception Management Application Block)などの部品を活用するとよい。

さて、以上が開発環境及び運用環境において行うべき「例外の後処理」ですが、実際には上記のような例外処理機能を組み込んだ上で、さらに出力された例外ログをきちんと読めるようになる必要があります。次回のエントリでは、出力された例外ログの読み方について解説することにしたいと思います。

……っつーかすみません、エントリ長すぎた><。2 時間ぐらいで書けるかなぁとか思ってたら、余裕で 5 時間ぐらいかかってしまいましたよ….orz というわけでえいや、っと投稿~。

Comments (0)

Skip to main content