可能你并不喜欢错误日志记录器的默认输出格式。它与所有其他系统所使用的格式确实有较大的差异。你所在的企业可能已经围绕自己的日志格式开发了大量工具,这些工具无法与Erlang的日志格式兼容。这时你该怎么办呢?还好,错误日志记录器允许你在日志系统中穿插自定义的逻辑并输出自定义的错误信息。
日志功能是构筑在Erlang的事件处理框架之上的,而该框架又以gen_event行为模式为基础。该行为模式为事件处理器封装了简单易用的接口。要想进一步调整Erlang/OTP的日志框架,就得编写新的gen_event行为模式的实现模块,好在这个任务并不难。gen_event行为模式接口gen_server的类似:其中包含你所熟悉的init、code_change和terminate回调函数,也包含handle_call和handle_info回调。不过gen_event接口用handle_event/2取代了handle_cast/2,你大概猜到了,这儿正是你接收错误日志事件的地方。
gen_event和gen_server之间的一个重要区别在于当你启动新的gen_server容器时,你需要告诉它应该使用哪个回调模块(这样也就可以了);但在启动gen_event容器(有时也被称作事件管理器)时,起初是无须任何回调模块的。相反,在容器完成初始化之后,可以动态添加(或删除)一个或多个处理器。当事件被投递至事件管理器时,事件管理器会调用当前已注册的所有处理器模块来处理事件。二者之间的区别如图所示:
正是由于这种一对多的关系,在实现了gen_event行为模式的回调模块中一般是找不到start_link函数的;即便有,该函数一般也都会先检查容器进程是否已经启动(当然,仅在容器进程是经过注册的单例进程时这么做才有意义)。另外请记住,事件管理器要调用的回调模块可不止这一个,因此,不要对事件管理器进程的状态做出什么异乎寻常的举动,至于其他处理器,只能祈祷它们同样守规矩了。
和gen_server一样,为了便于访问,gen_event进程启动时也可以有一个注册名。接下来,你将向注册名为error_logger的标准系统进程中添加一个处理器,该进程在所有Erlang/OTP系统中都存在。(这正是SASL启动时的工作。)当你调用error_logger模块中的日志函数时,所有日志事件都会被发送给这个进程。error_logger模块中还有一个专用于添加报告处理器的API函数,有了它你就无须关心事件处理器进程的定位问题了﹔该函数知道应该把处理器添加至哪个进程,并会连同该进程的注册名一起将调用委托给gen_event :add_handler/3。
你即将开发的简单日志事件处理器的骨架参见代码custom_error_report.erl。这只是错误日志记录器gen_event行为模式的一个最简单的实现。接收事件后它只会说:“OK,继续吧。”别的就什么也不会了。
-module(custom_error_report).
-behaviour(gen_event).
-export([register_with_logger/0]).
-export([init/1, handle_event/2, handle_call/2,
handle_info/2, terminate/2, code_change/3]).
-record(state, {}).
register_with_logger() ->
error_logger:add_report_handler(?MODULE).
init([])->
{ok, #state{}}.
handle_event(_Event, State) ->
{ok, State}.
handle_call(_Request, State) ->
Reply = ok,
{ok, Reply, State}.
handle_info(_Info, State) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
现在你只需重新编译模块并调用模块中的API函数custom_error_report:register_with_logger()将它挂载到错误日志记录器的事件流中即可。
?接收到事件后就要进行处理。在当前这个例子中,我们仅需要将它们输出到屏幕。要恰当地展现这些事件,就必须了解它们的确切含义。error_logger中的函数会产生一组特定的事件。Erlang/OTP文档对此作了总结。
调用汇报函数时,如果没有指定类型,事件将被赋予默认的报告类型。其中由error_rep-ort、warning_report和info_report标记的事件,默认类型分别为std_error、std_warn-ing和std_info。除了这3个类型之外,任意类型标识符都可用于用户自定义的报告类型。Gleader(进程组主管)字段暂且可以忽略,该字段用于指定标准输出的目的地。
?
这段代码的作用仅仅是将数据以略为不同的格式直接打印至标准输出,但通过它你应该能明白该如何编写自定义插件。请注意,有时你会收到一些无法与上述格式列表相匹配的事件,它们一般都是些可以忽略的系统消息,但你仍然需要在最后加上一个通配子句来处理这些事件,说上一句“OK”就够了。
至此,我们已经完成了对Erlang日志基础功能中最重要的内容的介绍。
?