4 日志记录烹饪手册
使用和尤其是配置日志记录器有时可能很困难,因为有许多不同的选项可以更改,而且通常有多种方法可以实现相同的结果。本用户指南试图通过提供许多不同的使用日志记录器的示例来提供帮助。
有关使用日志记录器的实际用例的更多示例,Fred Hebert 的博客文章 Erlang/OTP 21 的新日志记录器 是一个很好的起点。
如果您发现本指南中缺少某些常见的日志记录器用法,请在 github 上提交一个拉取请求,其中包含建议的补充内容。
4.1 获取日志记录器信息
打印主要日志记录器配置。
1> logger:i(primary). Primary configuration: Level: notice Filter Default: log Filters: (none)
也可以使用 logger:get_primary_config() 获取配置。
另请参见
- logger:i()
- 配置 在日志记录用户指南中
打印所有处理程序的配置。
2> logger:i(handlers). Handler configuration: Id: default Module: logger_std_h Level: all Formatter: Module: logger_formatter Config: legacy_header: true single_line: false Filter Default: stop Filters: Id: remote_gl Fun: fun logger_filters:remote_gl/2 Arg: stop Id: domain Fun: fun logger_filters:domain/2 Arg: {log,super,[otp,sasl]} Id: no_domain Fun: fun logger_filters:domain/2 Arg: {log,undefined,[]} Handler Config: burst_limit_enable: true burst_limit_max_count: 500 burst_limit_window_time: 1000 drop_mode_qlen: 200 filesync_repeat_interval: no_repeat flush_qlen: 1000 overload_kill_enable: false overload_kill_mem_size: 3000000 overload_kill_qlen: 20000 overload_kill_restart_after: 5000 sync_mode_qlen: 10 type: standard_io
您还可以使用 logger:i(HandlerName) 打印特定处理程序的配置,或使用 logger:get_handler_config() 或 logger:get_handler_config(HandlerName) 获取特定处理程序的配置。
另请参见
- logger:i()
- 配置 在日志记录用户指南中
4.2 配置日志记录器
我的进度报告去哪里了?
在 OTP-21 中,默认的主要日志级别是 notice。这意味着默认情况下不会打印许多日志消息。这包括主管的进度报告。为了获取进度报告,您需要将主要日志级别提升到 info
$ erl -kernel logger_level info =PROGRESS REPORT==== 4-Nov-2019::16:33:11.742069 === application: kernel started_at: nonode@nohost =PROGRESS REPORT==== 4-Nov-2019::16:33:11.746546 === application: stdlib started_at: nonode@nohost Eshell V10.5.3 (abort with ^G) 1>
4.3 配置日志记录器格式化程序
为了更好地适应您现有的日志记录基础设施,日志记录器可以以您想要的任何方式格式化其日志消息。您可以使用内置的格式化程序,也可以构建自己的格式化程序。
单行配置
由于单行日志记录是内置格式化程序的默认设置,因此您只需提供空映射作为配置即可。以下示例使用 sys.config 来更改格式化程序配置。
$ cat sys.config [{kernel, [{logger, [{handler, default, logger_std_h, #{ formatter => {logger_formatter, #{ }}}}]}]}]. $ erl -config sys Eshell V10.5.1 (abort with ^G) 1> logger:error("Oh noes, an error"). 1962-10-03T11:07:47.466763-04:00 error: Oh noes, an error
但是,如果您只想为当前会话更改它,也可以这样做。
1> logger:set_handler_config(default, formatter, {logger_formatter, #{}}). ok 2> logger:error("Oh noes, another error"). 1962-10-04T15:34:02.648713-04:00 error: Oh noes, another error
另请参见
- logger_formatter 的配置
- 格式化程序 在日志记录用户指南中
- logger:set_handler_config/3
在日志条目中添加文件和行号
您可以使用格式化程序模板更改打印到日志中的内容
$ cat sys.config [{kernel, [{logger, [{handler, default, logger_std_h, #{ formatter => {logger_formatter, #{ template => [time," ", file,":",line," ",level,": ",msg,"\n"] }}}}]}]}]. $ erl -config sys Eshell V10.5.1 (abort with ^G) 1> logger:error("Oh noes, more errors",#{ file => "shell.erl", line => 1 }). 1962-10-05T07:37:44.104241+02:00 shell.erl:1 error: Oh noes, more errors
请注意,文件和行号必须由 logger:log/3 的调用者添加到元数据中,否则日志记录器将不知道它是从哪里调用的。如果您在 kernel/include/logger.hrl 中使用 ?LOG_ERROR 宏,则会自动添加文件和行号。
另请参见
- logger_formatter 的配置
- logger_formatter 的模板
- 日志记录器宏
- 元数据 在日志记录用户指南中
4.4 配置处理程序
将日志打印到文件
我们不是将日志打印到标准输出,而是将它们打印到旋转文件日志中。
$ cat sys.config [{kernel, [{logger, [{handler, default, logger_std_h, #{ config => #{ file => "log/erlang.log", max_no_bytes => 4096, max_no_files => 5}, formatter => {logger_formatter, #{}}}}]}]}]. $ erl -config sys Eshell V10.5.1 (abort with ^G) 1> logger:error("Oh noes, even more errors"). ok 2> erlang:halt(). $ cat log/erlang.log 2019-10-07T11:47:16.837958+02:00 error: Oh noes, even more errors
另请参见
- logger_std_h 的描述
- 处理程序 在日志记录用户指南中
仅调试处理程序
添加一个处理程序,该处理程序将 debug 日志事件打印到文件,而默认处理程序仅将最多 notice 级别事件打印到标准输出。
$ cat sys.config [{kernel, [{logger_level, all}, {logger, [{handler, default, logger_std_h, #{ level => notice }}, {handler, debug, logger_std_h, #{ filters => [{debug,{fun logger_filters:level/2, {stop, neq, debug}}}], config => #{ file => "log/debug.log" } }} ]}]}]. $ erl -config sys Eshell V10.5.1 (abort with ^G) 1> logger:error("Oh noes, even more errors"). =ERROR REPORT==== 9-Oct-2019::14:40:54.784162 === Oh noes, even more errors ok 2> logger:debug("A debug event"). ok 3> erlang:halt(). $ cat log/debug.log 2019-10-09T14:41:03.680541+02:00 debug: A debug event
在上面的配置中,我们首先将主要日志级别提升到最大,以便调试日志事件能够到达处理程序。然后,我们将默认处理程序配置为仅记录 notice 及以下事件,处理程序的默认日志级别是 all。然后,调试处理程序配置了过滤器以阻止任何不是调试级别消息的日志消息。
也可以使用 logger 模块在已经运行的系统中进行相同的更改。然后你这样操作
$ erl 1> logger:set_handler_config(default, level, notice). ok 2> logger:add_handler(debug, logger_std_h, #{ filters => [{debug,{fun logger_filters:level/2, {stop, neq, debug}}}], config => #{ file => "log/debug.log" } }). ok 3> logger:set_primary_config(level, all). ok
重要的是,在调整默认处理程序的级别之前不要提升主要日志级别,否则您的标准输出可能会被调试日志消息淹没。
另请参见
- logger_std_h 的描述
- 过滤器 在日志记录用户指南中
4.5 日志记录
记录什么以及如何记录
记录某些内容的最简单方法是使用日志记录器宏,并向宏提供一个报告。例如,如果您想记录错误
?LOG_ERROR(#{ what => http_error, status => 418, src => ClientIP, dst => ServerIP }).
这将在默认日志中打印以下内容
=ERROR REPORT==== 10-Oct-2019::12:13:10.089073 === dst: {8,8,4,4} src: {8,8,8,8} status: 418 what: http_error
或者如果您使用单行格式化程序,则打印以下内容
2019-10-10T12:14:11.921843+02:00 error: dst: {8,8,4,4}, src: {8,8,8,8}, status: 418, what: http_error
另请参见
- 日志消息 在日志记录用户指南中
报告回调和事件打印
如果您想进行结构化日志记录,但仍然想要对最终日志消息的格式有一些控制,您可以将 report_cb 作为元数据的一部分提供给您的日志事件。
ReportCB = fun(#{ what := What, status := Status, src := Src, dst := Dst }) -> {ok, #hostent{ h_name = SrcName }} = inet:gethostbyaddr(Src), {ok, #hostent{ h_name = DstName }} = inet:gethostbyaddr(Dst), {"What: ~p~nStatus: ~p~nSrc: ~s (~s)~nDst: ~s (~s)~n", [What, Status, inet:ntoa(Src), SrcName, inet:ntoa(Dst), DstName]} end, ?LOG_ERROR(#{ what => http_error, status => 418, src => ClientIP, dst => ServerIP }, #{ report_cb => ReportCB }).
这将打印以下内容
=ERROR REPORT==== 10-Oct-2019::13:29:02.230863 === What: http_error Status: 418 Src: 8.8.8.8 (dns.google) Dst: 192.121.151.106 (erlang.org)
请注意,打印内容的顺序已更改,我还添加了 IP 地址的反向 DNS 查找。这在使用单行格式化程序时不会打印得那么漂亮,但是您也可以使用带 2 个参数的 report_cb 函数,其中第二个参数是格式化选项。
另请参见
4.6 过滤器
过滤器用于在日志事件到达处理程序之前删除或更改它们。
进程过滤器
如果我们只想记录特定进程的调试消息,可以使用这样的过滤器来实现
%% Initial setup to use a filter for the level filter instead of the primary level PrimaryLevel = maps:get(level, logger:get_primary_config()), ok = logger:add_primary_filter(primary_level, {fun logger_filters:level/2, {log, gteq, PrimaryLevel}}), logger:set_primary_config(filter_default, stop), logger:set_primary_config(level, all), %% Test that things work as they should logger:notice("Notice should be logged"), logger:debug("Should not be logged"), %% Add the filter to allow PidToLog to send debug events PidToLog = self(), PidFilter = fun(LogEvent, _) when PidToLog =:= self() -> LogEvent; (_LogEvent, _) -> ignore end, ok = logger:add_primary_filter(pid, {PidFilter,[]}), logger:debug("Debug should be logged").
需要进行一些设置以允许过滤器决定是否允许特定进程进行日志记录。这是因为默认的主要日志级别是 notice,它在主要过滤器之前执行。因此,为了使 pid 过滤器有用,我们必须将主要日志级别提升到 all,然后添加一个级别过滤器,该过滤器只允许某些大于或等于 notice 的消息通过。完成设置后,添加允许特定 pid 通过的过滤器就很简单了。
请注意,通过过滤器而不是通过级别进行主要日志级别过滤要昂贵得多,因此请确保在生产节点上启用它之前测试您的系统是否可以处理额外的负载。
另请参见
- 过滤器 在日志记录用户指南中
- logger_filters:level/2
- logger:set_primary_config/2
域
域用于指定特定日志事件源自哪个子系统。默认处理程序默认情况下只记录带有 [otp] 域或没有域的事件。如果您想将 SSL 日志事件包含到默认处理程序日志中,您可以这样做
1> logger:add_handler_filter(default,ssl_domain, {fun logger_filters:domain/2,{log,sub,[otp,ssl]}}). 2> application:ensure_all_started(ssl). {ok,[crypto,asn1,public_key,ssl]} 3> ssl:connect("www.erlang.org",443,[{log_level,debug}]). %% lots of text