Simple Cache真要是在运行过程中出了什么故障该怎么办呢?就当前的设计来看,除非服务整个崩溃,否则你可能根本无法察觉有任何异常。(比如,在我们的设计中,元素存储进程在退出时会自行进行清理,而由于工作进程被设置成temporary型,监督者此时是不会作出任何反应的。)
这套缓存服务不能再这样沉默下去了,Erlang/OTP自带的精良装备可以很好地解决这个问题。这些装备就是日志应用、SASL应用,以及由gen_event行为模式提供的事件处理框架。它们共同构成了一整套强大的用于与外界进行各种信息交换的工具。利用gen_event,你还可以让其他应用接入你的系统。
Log4j等日志系统(或是log4c、log4r等)。在各种软件开发工具中,日志的地位十分显赫,几乎所有编程语言都提供了一套适用于该语言的、可视为事实标准的日志系统。日志系统一般都设有多个严重级别,用于区分日志的重要程度。日志级别通常可分为五个,分别是: critical(或severe)、error、warning、info和debug。确切名称可能依系统的不同而不同。这几个级别的含义都显而易见,但每个级别的使用场合却没那么容易区分,针对这个问题,我们简单做一个总结。
critical或severe———表示系统遭遇了灾难性故障或者客户已无法访问系统,此时应立即采取人工措施。使用这个级别时应慎之又慎,只有那种有必要在凌晨三点把人从床上拖起来的紧急情况才用得上这个级别的日志。
error一一告知系统运维人员系统中出现了一些不良状况,但并不严重。例如,某个子系统崩溃后重启了一次,或是某个客户会话因数据错误而被中断。这类问题理应立即得到修复,不过拖到明天再处理也无伤大雅。不要滥用这个级别,否则人们会逐渐忽略这些消息。
warn一一告知运维人员系统中出现了某些潜在的负面问题,但暂时无害。你可以选择忽略这个级别的问题,或是暂时绕过留待日后修复,以免捅出别的什么篓子或是给系统平添不必要的负载。
info———表示一条通告性消息,用于将某个事件的发生告知给运维人员。这类事件可能是好消息,比如“备份任务完成";也可能有那么点儿令人沮丧,比如“邮件发送失败,五分钟后重试”。这个级别可以随便用,但也别太离谱,以免把你的运维团队淹没在各种毫无用途的细节之中。
debug———提供各种巨细信息。这个级别基本上是给身为开发者的你准备的,用于协助调试运行中的系统,(在一定限度内) debug日志总是越多越好。除非有人明确地要求,否则一般是不输出debug消息的。
大多数日志系统都允许运维人员根据具体情况设置一个最低严重级别。如果想要把控一切,就调到debug级别,在该级别下你可以看到所有类型的消息。如果希望看到除debug消息以外的所有消息,就调到info级别。如果仅希望在发现问题时得到通知,则可以调到warn级别,以此类推。
日志系统还提供了一些其他功能,比如调整输出格式,给日志消息添加时间戳等。暂且不去关注这些细节,我们先来看看Erlang/OTP的日志功能是怎样运作的。
日志是个极为常用的系统需求,Erlang/OTP的基本发行版便提供了日志功能支持。其主要功能由标准库中kernel应用的error_logger模块提供,供OTP行为模式使用的扩展日志功能则由SASL应用提供。它不仅提供了输出日志的方法,还给出了一套用于实现自定义日志和事件处理的通用框架。
OTP的默认日志格式比较古怪,常用的日志解析工具都拿它没办法。因此你必须权衡是否选用原生的日志系统,如果你的系统需要融入现有的非Erlang的基础架构,那么可能不应该选用其他的日志系统;如果是要基于OTP创建全新的系统,情况则相反。同时,你也应当明了在选择外部日志系统时需要做出哪些妥协。为了做到知己知彼,让我们先来看看日志系统的主要API。
用于投递日志消息的标准API比较简单,但该API仅支持3个日志级别:error、warning和info。很快你便会发现,这根本算不上什么限制,因为报首尖型N事PTL理m即ror四r蕴地(付干Fkernel于初学者而言,还是先将就一下吧。日志投递相关的API函数可以在error_logger模块(位于kernel应用中)中找到。以下是最基本的几个函数:
error logger:error_msg ( Format) -> ok.
error_logger:error_msg ( Format, Data) -> ok.
error_logger:warning_msg (Format) -> ok
error_logger:warning_msg (Format,Data) -> ok.
error_logger:info_msg (Format) ->ok.
error_logger:info_msg (Format, Data) -> ok.
这几个函数的接口与标准库函数io:format/1和io:format/2相同:第一个参数是格式字符串,其中可以包含由波浪号(~)开头的转义码,如~w,第二个参数则是与转义码对应的值的列表。
我们先来打印几条日志看看。使用下列方法你可以通过调用info_msg/1来输出简单字符串:
2>error_logger:info_msg("This is a message~n") .
=INFO REPORT==== 16-Jan-2024::22:21:32 ===
This is a message
ok
这样便向日志记录器发送了一条info消息,日志记录器会对该消息进行格式化,并给它加上一个含有严重级别和时间戳的标题。默认情况下,日志消息是直接输出到终端的,你也可以对系统进行配置,将日志输出到文件或将日志关闭。(示例中的ok是函数调用返回给shell的返回值,并非消息的一部分)
使用info_msg/2可以在日志消息中掺入数据:
3>error_logger:info_msg("This is an ~s message~n",["info"]).
=INFO REPORT==== 16-Jan-2024::22:24:24 ===
This is an info message
ok
转义码~s用于在格式字符串中插人其他字符串。有关格式字符串的详情请参阅io:format/2的文档,你只需记住第二个参数一定是个项式列表就可以了。一般来说,列表中的元素应该与格式字符串中的格式指示符一一对应(表示换行符的~n除外)。
这几个函数的容错性要优于io:format(...)。即便格式规范有误,消息也能输出,而不至于引起崩溃。例如,如果第二个参数中的元素数目不对,你将得到以下报告:
4> error_logger:info_msg ( "This is an ~s message~n", ["info",this_is_an_unused_atom]).
=INFO REPORT==== 16-Jan-2024::22:28:18 ===
ERROR: "This is an ~s message~n" - ["info",this_is_an_unused_atom]
ok
如此一来即便在写代码时不小心搞错了日志消息格式也不至于丢失潜在的关键信息。这个特性看似没什么大不了的,但却非常重要。
让我们来考察一些更为实用的日志消息吧:
5>error_logger:info_msg("Invalid reply ~p from ~s ~n",[<<"quux">>,"stockholm"]).
=INFO REPORT==== 16-Jan-2024::22:33:28 ===
Invalid reply <<"quux">>from stockholm
ok
当然,实际系统中的数据总是通过变量传入的,不会像这样硬编码在代码中。请注意~p:这个格式指示符特别有用。它可以将给定的值格式化成更易于人类阅读的格式—-在处理包含字符数据的列表或二进制串时尤为有用。
除了info_msg,请再分别尝试一下error_msg和warning_msg,并注意其中的区别。你会发现warning消息和error消息实际上是一样的;这是因为,在默认情况下, warning被直接映射至error。(出于历史原因,实际上只存在info和error两种消息。在启动Erlang时给erl加上+Ww参数可以取消warning到error的映射)