错误日志
OTP系统已经内置了一个可定制的错误日志模块。我们可以从三种不同的视角来看错误日志。程序员的视角关注代码中要记录一个错误日志的函数调用?配置的视角关注错误日志如何存储以及保存在哪里?报告的视角则关心错误发生之后,如何进行分析。我们将逐条讲述这些内容。
记录一个错误日志
在程序员的视角,错误日志的API很简单,下面是这些API的一部分:
@spec error_logger:error_msg(String) -> ok
向错误日志发送一个错误消息
1> error_logger:error_msg("An error has occurred ").
=ERROR REPORT==== 28-Mar-2007::10:46:28 === 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 ", ["Joe"]).
=ERROR REPORT==== 28-Mar-2007::10:47:09 === 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==== 28-Mar-2007::10:51:51 ===
tag1: data1
a_term tag2: data
需要说明的是,这只是可用错误日志API中的一小部分。详细讨论这些API没啥意思。下面的例子中,我们也只会用到error_msg。完整的细节可以参考手册中error_logger的部分。
配置错误日志
可以对错误日志进行多种配置。在Erlang的shell中我们可以看到所有的错误信息(如果我们什么都不配,这就是默认的配置)。我们可以把报告给shell中的错误写到一个格式化的文本文件中。此外,我们还能创建一个循环日志。你可以把循环日志想像成一个由错误日志产生的巨大环形缓存区域。新的消息进来时,会把它加到日志的尾部,如果日志被装满了,最早的条目就会被清除。
循环日志是一个极有用的特性。你可以决定总共有多少个日志文件,以及单个日志文件的大小。系统负责帮你删除旧文件以及创建新文件,以维持整个巨大的环形数据区域。你可以调整日志大小,以用来记录最近几天的操作,这在大多数情况下都是绰绰有余的了。
标准错误日志
启动Erlang的时候,我们可以给系统设置一些启动参数:
$ erl -boot start_clean
这会创建一个适合程序开发的环境。只会提供错误日志的简单形式(不带boot参数的erl启动命令其效果等同于erl –boot start_clean)。
$erl –boot start_sasl
这会创建一个适合产品化系统的环境。SASL是System Architecture Support Libraries的缩写,它负责错误日志,过载保护等等。
谁也没法记住日志记录器的所有配置(也没这个必要),所以,最好还是把错误日志文件的配置信息记到一个配置文件中。 下面的小节,我们会考察默认配置下系统中的错误日志是如何工作的。然后会了解四种典型的配置方式下,错误日志不同的工作方式。
不进行配置的SASL
这是在SASL下启动,不进行配置的情况:
$ erl -boot start_sasl
Erlang (BEAM) emulator version 5.5.3 [async-threads:0] ...
=PROGRESS REPORT==== 27-Mar-2007::11:49:12 ===
supervisor: {local,sasl_safe_sup}
started: [{pid,<0.32.0>},
{name,alarm_handler},
{mfa,{alarm_handler,start_link,[]}},
{restart_type,permanent},
{shutdown,2000},
{child_type,worker}]
... many lines removed ...
Eshell V5.5.3 (abort with ^G)
现在我们调用error_logger的方法来报告错误:
1> error_logger:error_msg("This is an error ").
=ERROR REPORT==== 27-Mar-2007::11:53:08 === This is an error
ok
注意,错误是在Erlang的shell中报告出来的。错误报告取决于错误日志记录器的配置。
控制记录何种日志
错误日志记录器会产生几种类型的报告:
Supervisor报告
在Supervisor启动或者停止被监管的进程时,会产生这个报告(我们在18.5节监管树“第345页”中会讨论它)。
Progress报告
每次OTP监管进程启动或者停止的时候会产生这个报告。
Crash报告
当被监管的进程退出时,如果它的退出原因不是normal或者shutdown,就会产生这个报告。
这三种报告是自动产生的,程序员无需关心。除此之外,当程序显式调用error_handler的方法时,也会产生三种日志报告。通过这三种报告,程序可以记录错误,警报以及提示信息。在这里,这三个术语并没有什么特别的语意,仅仅是程序员可以使用的三种标签,用来标明错误日志条目的自然属性(也就是说,想怎么用,随你)。
在日后对错误日志进行分析的时候,我们可以用这三种标签协助我们来检查问题,过滤日志条目。在对日志进行配置的时候,我们也可以指定,比如,只保存错误,其他的信息不予保存。下面我们就来写一个这样的配置文件。
elog1.config
%% no tty
[{sasl, [
{sasl_error_logger, false}
]}].
如果用这个配置文件启动系统,只有错误报告会被记录,progress报告之类的全部被忽略掉。而且,所有的错误都报告在shell当中。
$ erl -boot start_sasl -config elog1
1> error_logger:error_msg("This is an error ").
=ERROR REPORT==== 27-Mar-2007::11:53:08 === This is an error
Ok
文本文件和shell
无配置方法:
erl
-boot start_sasl -sasl sasl_error_logger {file,"path/to/logfile.log"}
%%
有些{}不需转义.
下一个配置文件,错误信息会同时出现在shell和一个文本文件之中:
elog2.config
%% single text file - minimal tty
[{sasl, [
%% All reports go to this file
{sasl_error_logger, {file, "/home/joe/error_logs/THELOG" }}
]}].
我们启动Erlang,生成一些错误信息,然后看看日志文件,以检查这个配置文件的效果。
$ erl -boot start_sasl -config elog2
1> error_logger:error_msg("This is an error ").
=ERROR REPORT==== 27-Mar-2007::11:53:08 === This is an error ok
我们可以查看/home/joe/error_logs/THELOG文件的内容,应该是这样的:
=PROGRESS REPORT==== 28-Mar-2007::11:30:55 ===
supervisor: {local,sasl_safe_sup}
started: [{pid,<0.34.0>},
{name,alarm_handler},
{mfa,{alarm_handler,start_link,[]}},
{restart_type,permanent},
{shutdown,2000},
{child_type,worker}]
...
循环日志和Shell
这个配置下,日志会在Shell以及一个循环日志中同时输出。这是一个很有用的配置。
elog3.config
%% rotating log and minimal tty
[{sasl, [
{sasl_error_logger, false},
%% define the parameters of the rotating log
%% the log file directory
{error_logger_mf_dir,"/home/joe/error_logs" },
%% # bytes per logfile
{error_logger_mf_maxbytes,10485760}, % 10 MB
%% maximum number of logfiles
{error_logger_mf_maxfiles, 10}
]}].
$erl -boot start_sasl -config elog3
1> error_logger:error_msg("This is an error ").
=ERROR REPORT==== 28-Mar-2007::11:36:19 === This is an error
False
当用这个配置运行系统的时候,所有的错误也会输出到这个循环日志中。本章后面的章节,我们会看到从日志中提取错误信息的方法。
产品化环境
在产品化环境中,我们只关注错误,而对progress和information日志不感兴趣。所有我们在配置中让日志记录器仅仅记录错误。以免这些信息淹没在大量的progress和information日志中。
elog4.config
%% rotating log and errors
[{sasl, [
%% minimise shell error logging
{sasl_error_logger, false},
%% only report errors
{errlog_type, error},
%% define the parameters of the rotating log
%% the log file directory
{error_logger_mf_dir,"/home/joe/error_logs" },
%% # bytes per logfile
{error_logger_mf_maxbytes,10485760}, % 10 MB
%% maximum number of
{error_logger_mf_maxfiles, 10}
]}].
运行的时候,这个配置与前一个配置的效果看起来很相似,唯一的区别在于,只有错误会被记录到日志中。
分析错误日志
读取错误日志是rb模块的职责。它的接口相当简单。
1> rb:help().
Report Browser Tool - usage
===========================
rb:start() - start the rb_server with default options rb:start(Options) - where Options is a list of:
{start_log, FileName}
- default: standard_io
{max, MaxNoOfReports}
- MaxNoOfReports should be an integer or 'all'
- default: all
...
... 此处省略很多行 ...
...
我们启动report browser的时候可以指定它需要读取多少行(这个例子中,是最后的20行)
2> rb:start([{max,20}]).
rb: reading report...done.
3> rb:list().
No Type Process Date Time
== ==== ======= ==== ====
11 progress <0.29.0> 2007-03-28 11:34:31
10 progress <0.29.0> 2007-03-28 11:34:31
9 progress <0.29.0> 2007-03-28 11:34:31
8 progress <0.29.0> 2007-03-28 11:34:31
7 progress <0.22.0> 2007-03-28 11:34:31
6 progress <0.29.0> 2007-03-28 11:35:53
5 progress <0.29.0> 2007-03-28 11:35:53
4 progress <0.29.0> 2007-03-28 11:35:53
3 progress <0.29.0> 2007-03-28 11:35:53
2 progress <0.22.0> 2007-03-28 11:35:53
1 error <0.23.0> 2007-03-28 11:36:19
ok
> rb:show(1).
ERROR REPORT <0.40.0> 2007-03-28 11:36:19
===========================================================
This is an error ok
要隔离一个特定的错误,我们可以使用rb:grep(RegExp)命令。它会找出所有匹配这个正则表达式的记录。我这里就不深入分析错误日志的细节问题了,学习它的最好方法是花一点时间,自己来和rb进行一些交互,看看都能做点什么。注意,你根本不需要自己来删日志,循环机制会删除旧的日志。
如果你想要保留所有的日志,那么你就需要每隔一段特定的时间过来抓取,并自己进行清理。