この章を読む場合には、すべてのインタフェース関数やコールバック関数の詳細が書かれている、 gen_event(3) と一緒に読んでください。
OTP内には、 イベント を送ることができる、 イベントマネージャ という名前付きのオブジェクトがあります。イベントというのは、例えばエラーや警告、その他のログに保存すべき情報群などが考えられます。
イベントマネージャには イベントハンドラ を複数インストールすることができます。イベントマネージャにイベントの通知がくると、インストールされたすべてのイベントハンドラで処理されます。例えば、エラーを取り扱うイベントマネージャをデフォルトでインストールすることができます。このイベントハンドラは、エラーメッセージをコンソールに書きだします。もし、エラーメッセージを一定期間ファイルに保存したいのであれば、そのようなイベントハンドラを作って登録すれば実現することができます。ログをファイルに保存する必要がなくなれば、そのイベントハンドラは削除されます。
イベントマネージャはプロセスとして実装され、イベントハンドラはコールバックモジュールとして実装されます。
イベントマネージャは、 {モジュール, 状態} というペアのリストとして保持されます。このモジュールはイベントハンドラ、状態はイベントハンドラの内部ステートになります。
エラーメッセージをコンソールに出力するイベントハンドラの実装となるコールバックモジュールのサンプルコードです。
-module(terminal_logger).
-behaviour(gen_event).
-export([init/1, handle_event/2, terminate/2]).
init(_Args) ->
{ok, []}.
handle_event(ErrorMsg, State) ->
io:format("***Error*** ~p~n", [ErrorMsg]),
{ok, State}.
terminate(_Args, _State) ->
ok.
エラーメッセージをファイルに書き出すイベントハンドラの実装となるコールバックモジュールのサンプルコードです。
-module(file_logger).
-behaviour(gen_event).
-export([init/1, handle_event/2, terminate/2]).
init(File) ->
{ok, Fd} = file:open(File, read),
{ok, Fd}.
handle_event(ErrorMsg, Fd) ->
io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
{ok, Fd}.
terminate(_Args, Fd) ->
file:close(Fd).
コードについては次のセクションで説明します。
上記で説明したエラーハンドリングのためのイベントマネージャを起動するには、次の関数呼び出しをします。
gen_event:start_link({local, error_man})
この関数は、新しいプロセスをspawnして、イベントマネージャと結びつけます。
引数の {local, error_man} を使って名前を設定します。この場合には、イベントマネージャはローカルな error_man という名前で登録されます。
もし名前が省略されると、イベントマネージャは登録されません。その代わり、Pidを使う必要があります。名前には {global, 名前} という質得もできます。この場合、イベントマネージャは global:register_name/2 を使って登録されます。
イベントマネージャを監視ツリーの一部として仕様する場合には gen_event:start_link を使用すると、スーパバイザによって起動されます。スタンドアローンのイベントマネージャを起動するには、 gen_event:start という別の関数があります。この場合、監視ツリーの一部にはなりません。
次のコードは、イベントマネージャを起動し、イベントハンドラを追加します。対話モード内で動かしています。
1> gen_event:start({local, error_man}).
{ok,<0.31.0>}
2> gen_event:add_handler(error_man, terminal_logger, []).
ok
この関数は、イベントマネージャに対して、error_manとして登録するというメッセージを送り、terminal_loggerのイベントハンドラを追加するように伝えます。イベントマネージャは、コールバック関数の terminal_logger:init([]) を呼び出します。この [] は、 add_handler の3番目の引数で、イベントハンドラの内部ステートを表します。
init(_Args) ->
{ok, []}.
ここでは、 init は入力データを必要としておらず、それを無視しています。terminal_loggerも内部ステートを利用してません。file_loggerはオープンしたファイルのデスクリプタを保存するために、内部ステートを利用しています。
init(File) ->
{ok, Fd} = file:open(File, read),
{ok, Fd}.
3> gen_event:notify(error_man, no_reply).
***Error*** no_reply
ok
error_manはイベントマネージャの名前で、no_replyがイベントです。
イベントはメッセージになって、イベントマネージャに送られます。イベントを受け取ると、イベントマネージャは handler_event(Event, State) を、すべての登録されたイベントハンドラに対して、追加された順序で呼び出します。この関数は {ok, ステート1} というタプルを返すことが期待されています。この「ステート1」はイベントハンドラの新しいステートの値として使われます。
terminal_loggerのソースコード:
handle_event(ErrorMsg, State) ->
io:format("***Error*** ~p~n", [ErrorMsg]),
{ok, State}.
file_loggerのソースコード:
- handle_event(ErrorMsg, Fd) ->
- io:format(Fd, “*Error* ~p~n”, [ErrorMsg]), {ok, Fd}.
4> gen_event:delete_handler(error_man, terminal_logger, []).
ok
この関数は、error_manという名前のイベントマネージャに対して、terminal_loggerというイベントハンドラを削除するように通知します。イベントマネージャはコールバック関数の terminal_logger:terminate([], State) を呼び出します。この時の引数は、 delete_handler の3番目の引数です。 terminate 関数は、 init 関数の逆で、必要な後処理を行います。返り値は無視されます。
teminal_loggerの場合には後処理は不要:
terminate(_Args, _State) ->
ok.
file_loggerの場合には、 init の中で開いたファイルデスクリプタをクローズする必要があります。
terminate(_Args, Fd) ->
file:close(Fd).
イベントマネージャが停止させられると、 terminate/2 を呼び出して、削除と同様にすべての登録済みのイベントハンドラの後片付け処理を行わせることができます。
もし、イベントマネージャを監視ツリーの中で動かすのであれば、終了関数は不要です。監視ツリーが自動的にイベントマネージャを終了させます。正確には、スーパバイザの shutdown_strategy を定義することで作業が完了します。
イベントマネージャが監視ツリーの一部でない場合には次のように stop 関数を呼び出すと停止させることができます。
> gen_event:stop(error_man).
ok
Copyright (c) 1991-2009 Ericsson AB