event_handler.erl
-module(event_handler).
-export([make/1, add_handler/2, event/2]).
make(Name) ->
register(Name, spawn(fun() -> my_handler(fun no_op/1) end)).
add_handler(Name, Fun) -> Name ! {add, Fun}.
event(Name, X) -> Name ! {event, X}.
my_handler(Fun) ->
receive
{add, Fun1} ->
my_handler(Fun1);
{event, Any} ->
(catch Fun(Any)), my_handler(Fun)
end.
no_op(_) -> void.
创建了一个“啥也不干”的事件处理程序,它的名字是Name(一个原子)。这提供了一个可以发送事件的位置。
event_handler: event(Name,X)
向Name事件处理程序发送一个X事件。
event_handler:add_handler(Name,Fun)
给Name事件处理程序增加一个事件处理方法Fun。现在,当事件X发生的时候,事件处理程序会执行Fun(X)。
现在我们就来建立一个事件处理程序,并产生一个错误事件:
1>event_handler:make(errors).
true
2>event _handler:event(errors,hi).
{event,hi}
我们可以看到什么也没发生,因为我们没有在事件处理程序中安装回调模块。
要让事件处理程序做点事,我们需要写一个回调模块,然后把它安装到事件处理程序中去。下面的代码就是一个事件处理程序回调模块:
motor_controller.erl
-module(motor_controller).
-export([add_event_handler/0]).
add_event_handler() ->
event_handler:add_handler(errors, fun controller/1).
controller(too_hot) ->
io:format("Turn off the motor~n");
controller(X) ->
io:format("~w ignored event:~p~n", [?MODULE, X]).
编译,然后安装它:
3>c(motor_controller) .
{ok,motor_controller}
4>motor_controller:add_event_handler().
{add,#Fun}
现在,我们来给这个处理程序发送一个事件,它应该被motor:controller:controller/1函数处理。
5>event_handler:event(errors,cool).
motor_controller ignored event: cool
{event,cool}
6>event_handler:event(errors,too_hot).
Turn off the motor
{event,too_hot}
首先,程序提供了一个可以发送事件的名字。这里它是注册为errors的一个进程。然后我们定义了向这个进程发送消息时所使用的协议。值得留意的一点是,我们这里并没有明确当事件到达的时候要做什么动作。实际上,这个时候做的动作就是noOp(X),它就是“什么也不做”的动作。之后,在下一步,程序安装了一个自定义的事件处理程序。
其实,所谓的事件处理程序,最为核心的概念就在于它提供了一个基础架构,我们可以安装自定义的处理程序。
错误日志的基础架构也遵循这一模式。这也就意味着我们同样可以在出错日志中安装各种处理程序,以便在事件发生的时候做不同的事情。除此之外,警报处理系统也遵循着相同的架构模式。
OTP系统已经内置了一个可自定义的错误日志模块。我们可以从3种不同的视角来看错误日志。程序员的视角关注代码中要记录一个错误日志的函数调用;配置的视角关注错误日志如何存储以及被保存在哪里;报告的视角则关心错误发生之后,如何进行分析。我们将逐条讲述这些内容。
错误日志的API很简单,下面是这些API的一部分
@spec error_logger:error-msg(String) -> ok
向错误日志发送一个错误消息。
1> error_logger: error_msg("An error has occurred\n").
ERROR REPORTmm= 4-Jan-2024::23:42:11 ===
An error has occurred
ok
@spec error_logger: error_msg(Format,Data) ->ok
向错误日志发送一个错误消息它的参数与io:format(Format,Data)函数的参数一样。
2> error _logger:error_msg("~s,an error has occurred\n",["Joe"]).
=ERROR REPORT=== 4-Jan-2024::24:23:17 ===
Joe,an error has occurred
ok
@spec error_logger:error_report(Report) -> ok
向错误日志发送一个标准错误报告。
@type Report = [{Tag,Data} | term()] | string() | term()]
@type Tag = term()
@type Data = term()
3>error_logger:error_report([{tag1,data1},a_term,{tag2,data}]).
=ERROR REPORT==== 4-Jan-2024::27:31:44 ===
tag1: data1
a_term
tag2: data
这些只是可用错误日志API中的一小部分
启动Erlang的时候,我们可以给系统设置如下一些启动参数。
$ erl -boot start_clean
这会创建一个适合程序开发的环境,只会提供错误日志的简单形式(不带启动参数的erl命令效果等同于erl -boot start_clean)。
$erl -boot start_sasl
这会创建一个适合产品化系统的环境。SASL是System Architecture Support Libraries的缩写,它负责错误日志、过载保护等。
这是在启动SASL时,不进行配置的情况:
$ erl -boot start_sasl
现在我们调用error_logger的方法来报告错误:
1>error_logger: error_msg("This is an error\n").
=ERROR REPORT==== 4-Jan-2024::30:34:24 ===
This is an error
ok
注意,错误是在Erlang shell中报告出来的,错误报告取决于错误日志记录器的配置。
错误日志记录器会产生下面几种类型的报告
监管报告。在监管进程启动或者停止被监管的进程时,会产生这个报告。
进程报告。每次OTP监管进程启动或者停止的时候会产生这个报告。
崩溃报告。当被监管的进程退出时,如果它的退出原因不是normal或者shutdown,就会产生这个报告。
这3种报告是自动产生的,我们无需关心。
在日后对错误日志进行分析的时候,我们可以用这3种标签协助我们来检查问题,过滤日志条目。在对日志进行配置的时候,我们也可以指定,比如,只保存错误,其他的信息不予保存。下面我们就来写一个这样的配置文件。
elog1.config
[{sasl,[{sasl_error_logger,false}]}].
如果用这个配置文件启动系统,只有错误报告会被记录,进程报告之类的全部被忽略掉。而且,所有的错误报告都在shell当中。
$ erl -boot start_sasl -config elogl
1> error_logger:error_msg("This is an error\n").
=ERROR REPORT==== 4-Jan-2024::34:24:29 ===
This is an error
ok
下一个配置文件列出了shell中的错误报告,同时在shell中报告的所有东西的副本也生成一个文件: elog2.config
[{sasl,[{sasl_error_logger,{file,"/src/error_logs/THELOG"}}]}].
我们启动Erlang,生成一些错误信息,然后看看日志文件以检查这个配置文件的效果。
$ erl -boot start_sasl -config elog2
1>error_logger:error_msg("This is an error\n").
=ERROR REPORT==== 4-Jan-2024::38:15:52 ===
This is an error ok
我们可以查看/src/error_logs/THELOG文件的内容.
日志会在shell以及一个循环日志中同时输出
elog3.config
[{sasl,[
{sas1_error_logger, false},
{error_logger_mf_dir, " /src/error_logs"},
{error_logger_mf_maxbytes, 10485760},
{error_logger_mf_maxfiles, 10}
]}].
$erl -boot start_sasl -config elog3
1>error_logger:error_msg("This is an error\n").
=ERROR REPORT==== 4-Jan-2024::53:47:22 ===
This is an error
false
当用这个配置运行系统的时候,所有的错误也会输出到这个循环日志中
elog4.config
[{sasl,[
{sasl_error_logger,false},
{errlog_type,error},
{error_logger_mf_dir, "/src/error_logs"},
{error_logger_mf_maxbytes,10485760},
{error_logger_mf_maxfiles,10}
]}].
运行的时候,这个配置与前一个配置的效果看起来很相似,区别在于,只有错误会被记录到日志中。