お題:イベントログを監視するサービスを作ろう

サーバーに溜まってるイベントログ。これを監視して、メールで通知してくれるようなサービスを作ってみます。

<用意したもの>
・Visual Studio 2005 (エディションに注意−Standard不可)
・Windows 2000 Server
・SMTPサーバー、メールクライアント(用意したってほどでもないけど)

<どうやって監視するのか>
まずは、イベントログを監視するのに、何が使えるかを調査しました。きっとFileSystemWatcherみたいなコンポーネントがあるだろうと思ったら…ねぇよ。
で、調べていくと、System.Diagnostics.EventLog に"EntryWritten"というイベントがあることが判明。これを使います。

参考:MSDN−方法:EntryWritten イベントのハンドラを作成する

<先ずはテンプレートを利用して、サービスプログラムの形を作る>
Visual Studioから新しいプロジェクトを作成します。ここではVBの"Windows サービス"を選択します。

で、OnStart、OnEnd、とかを記述していくわけです。その辺りは、以前blogでご紹介したコチラ−MSDN 方法:Windows サービスを作成する の内容です。

<起動条件違反はどうしようか…>
さて、メールで通知なので、SMTPサーバー名、Fromアドレス、Toアドレスは必須なわけです。
でもって、オテガルにApp.config - つまり、My.Settings で安直に設定することにしたのですが…
きちんと設定されていなかったときはどうしようか…

試行錯誤の結果、OnStartでチェックして、NGだったら例外をスローする形にしました。

★やってはいけない起動条件チェックの飛ばし方
・コンストラクタ(Sub Main)でチェックして、Exit Subさせる → サービス起動時に「応答がありませんでした」と怒られます。
・OnStartでエラーログを出力し、Exit Sub させる → 結局起動してしまう上に、エラーログが出力されなかった。

<10秒まつのだぞ…>
MSDN−EntryWritten イベント(System.Diagnostics)によれば、なんでもかんでも拾ってくれると言うものではないらしい。
つまり、「最新5秒の間のイベントだけ拾うから、一度イベントを拾ったら10秒ぐらいWaitしてね。」という注意書き。
と、いうわけで、イベントプロシージャーの末端に、10秒のWaitを入れる。こんな感じ…

mtimWaitTimer = New AutoResetEvent(False)
mtimWaitTimer.WaitOne(10000, False)

※こんなものをCONSTやMy.Settingsに逃がす必要を感じなかったので、ハードコード。

<監視する対象のイベントログごとに用意する…>
EventLog.Log に何も設定しないと、イキナリ怒られてしまいます。全部を監視してくれるなどという、都合の良い事態にはなってくれないのですね。
仕方なく、EventLog Objectを3つ作成し、それぞれに"Application","Security","System"を割り当てる。
 

 '---監視用イベントログオブジェクト
Private WithEvents mobjApplicationLog As EventLog '---アプリケーションログ用
Private WithEvents mobjSystemLog As EventLog '---システムログ用
Private WithEvents mobjSecurityLog As EventLog '---セキュリティログ用
mobjApplicationLog = New EventLog()
mobjApplicationLog.Log = "Application"

mobjSystemLog = New EventLog()
mobjSystemLog.Log = "System"

mobjSecurityLog = New EventLog()
mobjSecurityLog.Log = "Security"
 


で、それぞれに、EnableRaisingEvents プロパティを設定。OnStart , OnContinue でTUREに。OnEnd, OnPause でFALSEに。
これを設定しておかないと、イベントががらない、或いは、あがりっぱなし。

<要らないイベントはスルーの方向で…>
要らないイベントまで拾ってしまうと、メールの嵐になり、通知する意味がなくなってしまいます。
そこで、ここでは「情報」はスルーするようにします。

'---「情報」の場合には処理しない
If e.Entry.EntryType = EventLogEntryType.Information Then
 Exit Sub
End If

<で、通知する文面…>
EntryWritten イベントは、EntryWrittenEventArgs を渡してくれるわけです。
その詳細はコチラ−MSDN:EntryWrittenEventArgs クラス(System.Disagnostics)
で、その中に"Entry"ってのがあるので、それからモロモロと情報をもらってきて、文面にします。

<インストーラを用意する…>
デザイナ画面を右クリックして、インストーラーの追加。

コレをやらないと、サービスとして登録してくれません。
で、ServiceInstaller のプロパティにName,Descriptionなど、サービス名回りの大事なプロパティがあるので、忘れずに設定。


同じように、ServiceProcessInstaller にも、実行アカウント情報などがあるので、こちらも忘れずに。

<テスト用アプリケーションも忘れずに…>
まさか、イベントが発生するのを指をくわえて待っているわけにもいきません。
なので、簡単なイベントログ書き込みアプリをサクッと作成します。テスト用なので凝らない!
ただし、ココでの罠は、「セキュリティログには書き込めない」ということです。

セキュリティログは読み取り専用です。ムダな抵抗をしないこと。

<いよいよテスト…>
ま、世の中、たたきゃ埃な訳で、イロイロと激しいことになるわけです。落ち着いてやりましょう。
ありがちな間違いとしては、
サービスなのに普通に「デバック実行」してしまわないこと。
これは怒られます。手順としては…

1)installutil を使って、サービスをインストールする。installutil が見つからないなら、素直に[スタート]→[検索(C)]→[ファイルやフォルダ(F)] を使って、システムディレクトリ(\WINNT や \WINDOWS )下を検索。
2)コマンドラインから"Net Start" か、おなじみのサービスマネージャーを使って、サービスを起動。
3)Visual Studio から[ツール(T)]→[プロセスにアタッチ(P)]で、プロセス一覧を出す。
4)プロセス一覧から、当該サービスのプロセスを探す。この場合、"EventLogWatcher.exe"である。
  ここで、見つからなくても慌てない。「すべてのユーザーからのプロセスを表示する」のチェックを入れること。
  (先のServiceProcessInstaller のプロパティで設定したアカウントになっているので。)



5)これで目出度く、普通にデバックできるようになりました。
6)でバックが終わったら、まずは、Visual Stuido のデバック実行を終了します。
7)"net stop"か、サービスマネージャーから、サービスを停止させます。
8)"installutil /u"で、サービスを削除します。

これがデバックの一連の流れです。ここでの注意点は…
・デバック結果を元に改めてビルドするときは、サービスを止めておかないと怒られます。(別プロセスがつかんでいる格好になるので…)
・installutil を使うとき、面倒くさいのでバッチファイルを作っておくのは良いアイディアです。
 しかし、普通に作ると、登録エラーの時の情報が流れて消えていくので、そこは注意してください。
・サービスの起動・停止にGUIのサービスマネージャーを使うと、installutilが上手く動かないことがあります。そういう時は、サービスマネージャーを上げなおしてください。
 そういう意味で、コマンドラインからの"Net Start","Net Stop"がオススメです。
・サービスが起動できないときは、イベントログを確認。

で、このプロセスを繰り返して、デバックして、目出度くバグが取れたら…

<これで終わりではありません!>
ま、これで終わってしまっても良いのですが、改めてコードを見直しましょう。
キレイにできるところ、冗長なところ、そんなところを直しておきましょう。
たとえば…

Protected Overrides Sub OnStart()
 mobjApplicationLog.EnableRaisingEvents = True
 mobjSystemLog.EnableRaisingEvents = True
 mobjSecurityLog.EnableRaisingEvents = True
End Sub

Protected Overrides Sub OnEnd()
 mobjApplicationLog.EnableRaisingEvents = False
 mobjSystemLog.EnableRaisingEvents = False
 mobjSecurityLog.EnableRaisingEvents = False
End Sub

Protected Overrides Sub OnPause()
'---以下略

ってなコード、もっとキレイにできますよね。たとえばこんな感じ。
 

Protected Overrides Sub OnStart()
 ChangeEnableRaiseState(True)
End Sub

Protected Overrides Sub OnEnd()
 ChangeEnableRaiseState(False)
End Sub

Protected Overrides Sub OnPause()
'---以下略


Private Sub ChangeEnableRaiseState(ByVal blnEnableRaise As Boolean)
 mobjApplicationLog.EnableRaisingEvents = blnEnableRaise
 mobjSystemLog.EnableRaisingEvents = blnEnableRaise
 mobjSecurityLog.EnableRaisingEvents = blnEnableRaise
End Sub
 

でも、これでも未だダメですね。コメントもきちんとつけて、出来上がりです。

<で、ちゃんと読んでおくべきドキュメント…>
Windows Serviceの作り方に関する最も基本的なドキュメントはコチラ−MSDN Windows サービス アプリケーション から下にある全てのドキュメントです。
きちんと読んでおきましょう。
 

<完成品…>
完成品はコチラです。自己責任でお使いください。
ご使用の際は、必ず configファイル内の設定を然るべきものに変更してください。

メニューに戻る
フィードバックはコチラにコメントをどうぞ