[IT Pro 道場補足] WCF サービスからイベントログ (Event Log) を出力する
IT Pro 道場 (東京) 補足
ここからは、イベントログの活用術について説明します。
エラー処理は集中的に管理し、この集中管理されたリポジトリを使って統一的に運用の機能を作りこみたいものです。さて、IIS、SQL Server などなど多くの Windows 関連の製品がイベントログに対応していますが、このイベントログをデモでお見せしたような自作のアプリケーションからも使って、統一的な管理をしたくなることでしょう。
そこで以下では、自作のアプリで、イベントログをどう使っていくかという内容を記述します。(尚、ここでも、.NET Framework 3.5 の新しいクラスを使います。)
まずは、標準的な WCF サービスの場合についてです。
WCF では、.config を設定することでログの出力が可能です。しかし、この仕組みは、トレースを出力するもので、作成したアプリケーションのコード部分を含む特定の例外などを処理するためのいわゆるエラーログではありません。ですから、本番環境でこのような設定 (.config) を仕掛けてしまうと、やりとりされているメッセージの情報がトレースにどんどん書かれてしまいますので良くありません。(あくまでもデバッグやパフォーマンス検証などの目的で使う仕組みです。)
そこで、以下では、例外の処理を中心にイベントログに書き込む方法について説明します。
まず、前提知識として、以下の Web キャストを参照してください。(コードはほとんど出てきませんので、IT Pro の方も是非ご視聴ください。)
イベントログの活用
http://www.microsoft.com/japan/msdn/windows/windowsserver2008/tab/code/eventlog.aspx
ここでおこなっているデモのエッセンスをご説明しますと、C# や VB などマネージコードで作成した
カスタムアプリでもイベントログが簡単に扱えるというデモです。手順は、ビデオでご紹介しているように、
- まず、イベントマニフェストと呼ばれる XML を記述します。
この XML では、イベントログシステムに独自の入れ物を作成したり、そこへログをポストしたりするための基本的な設定情報が記載されています。
- つぎに、mc (マニフェストコンパイラ) というコマンドを実行して、上記の XML からリソースコンパイラに読み込ませるためのファイル (.rc ファイル) を作成します。
IT Pro の方向けに記載すると、これらの開発用のコマンドは、「Visual Studio コマンドプロンプト」というものを使用すると、Path が既に通っていますので、楽に開発できます。
- つぎに、rc (リソースコンパイラ) というコマンドを実行して、上記で作成した .rc ファイルから、リソースファイル (win32 用の .res ファイル) を作成します。
- つぎに、皆さんが作成したアプリケーションのコンパイルをおこなうのですが、このコンパイルオプションに、/win32res:[上記のリソースファイル] を付加してコンパイルします。
ここが少し面倒なのですが、実は、Visual Studio からこの処理を設定できないので(すみません、win32リソースを添付するだけであれば、プロジェクトのプロパティで指定可能です。但し、他のオプションと併用する場合にはコマンドからの実施になります)、csc.exe, vbc.exe などのコンパイラコマンドを直接たたきます。ちなみに、「コマンドでコンパイルするって、それってオプションも全部自分で考えないといけないから面倒!」と思われるかもしれませんが、Visual Studio でリビルドをおこなうと、実行されている csc や vbc のコマンドが [出力] ウィンドウに表示されますので、これをコピー/ペーストして利用すれば労力は減ることでしょう。
- 最後にイベントログシステムにログを受け付けるための設定をおこないます。
wevtutil というコマンドを使って設定します。
さて、同じ原理で WCF のサービスの場合でも、作成した WCF サービスやその中で使われる dll などにリソースを設定すれば良いわけです。(ちなみに、イベントプロバイダーでイベントを発行するプログラムと、win32 リソースを添付する dll や exe は同一である必要はありませんので、実際の開発ではモジュール性なども考慮し、イベント管理をおこなう実行プログラム上だけにリソースを添付しておく、などしておくと良いでしょう。)
しかし、1 つだけポイントがあります。
あとは WCF サービスのコードの中でログ出力の処理を書けばよいのですが、ここが問題です。無論、随時例外を拾って毎回ログを書いても良いのですが、例外処理はプログラムの中のあちこちに書くのではなく集中的に管理したいものです。WCF では、こうした場合に備え、IErrorHandler というエラーハンドリングの処理を独自に組み込む仕組みを持っています。
独自のエラー処理用クラスを作成し、これをWCFのクラスに属性として指定するだけで、catch されない例外が発生した場合、必ずそのエラー処理用のカスタムなクラスに飛ばすといったことが可能です。以下が、そのコードです。
(IT Pro の方は以下のコードの中身までは追わなくて結構です。何ができるのか、というエッセンスを理解しておいてください。以下は、「開発者」のために記載しておきます。)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Diagnostics.Eventing;
namespace WcfServiceLibrary1
{
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetName(int value);
}
// エラーハンドラの定義
public class MyErrorHandler : IErrorHandler
{
#region IErrorHandler メンバ
public bool HandleError(Exception error)
{
// イベントログへの出力実施!
EventDescriptor evtDesc = new EventDescriptor(1, 0, 0, 2, 0, 0, 0);
EventProvider evtProvider = new EventProvider(new Guid("{0A32B886-6D42-4342-8270-803C93359D20}"));
evtProvider.WriteEvent(ref evtDesc, error.Source, error.Message);
return true; // 処理した ! と返す
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// 本来は、ここで、fault にフォールトメッセージを設定する
// (今回は何もしない)
}
#endregion
}
// 上記のエラーハンドラを扱う属性(メソッド属性)の定義
public sealed class MyErrorBehaviorAttribute : Attribute, IServiceBehavior
{
MyErrorHandler myErrorHandler;
public MyErrorBehaviorAttribute() // 今回は属性の引数はなしとします
{
this.myErrorHandler = new MyErrorHandler();
}
void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
{
}
void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(myErrorHandler);
}
}
}
[MyErrorBehavior]
public class Service1 : IService1
{
public string GetName(int value)
{
throw new Exception("テスト用に例外を出してみた!");
return "";
}
}
}
2008/01/23 追記: こちらに ETW の仕組み、リスナーを使ったモデルなども含め統合的に記述しました -> ETWへのご招待
【開発者の方へ】
尚、先ほど、リソースの添付をおこなうには、「Visual Studio が出力するコマンドをそのままコピー/ペーストして使えば良い」 と記載しましたが、出力コマンドでは、obj の下に exe や dll を生成していますので注意しましょう。(つまり、イベントマニフェストに記述するパスが変わってきます。)
また、WCF サービスを参照してホストプログラムや WAS にホストした svc をコンパイルする場合、WCF サービスの bin 下の dll が、ホストプログラムの bin 下にコピーされていますが、この処理も、[出力] ウィンドウには表示されていません。さらに、参照先も obj 下ではなく bin 下の dll を参照しているはずです。
これらの点に注意して、パスの変更や、コピーの処理などを適宜埋め込んで、コンパイルをおこなってください。
マニフェストファイルのリソースの場所が間違っている場合、イベントビューアで該当のフォルダを参照すると (あるいは 、ETW のシステムがリソースを探しにいくと) エラーとなります。