4  日志烹饪手册

4 日志记录烹饪手册

使用和尤其是配置日志记录器有时可能很困难,因为有许多不同的选项可以更改,而且通常有多种方法可以实现相同的结果。本用户指南试图通过提供许多不同的使用日志记录器的示例来提供帮助。

有关使用日志记录器的实际用例的更多示例,Fred Hebert 的博客文章 Erlang/OTP 21 的新日志记录器 是一个很好的起点。

注意

如果您发现本指南中缺少某些常见的日志记录器用法,请在 github 上提交一个拉取请求,其中包含建议的补充内容。

1> logger:i(primary).
Primary configuration:
    Level: notice
    Filter Default: log
    Filters:
        (none)

也可以使用 logger:get_primary_config() 获取配置。

另请参见
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) 获取特定处理程序的配置。

另请参见

在 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>

为了更好地适应您现有的日志记录基础设施,日志记录器可以以您想要的任何方式格式化其日志消息。您可以使用内置的格式化程序,也可以构建自己的格式化程序。

由于单行日志记录是内置格式化程序的默认设置,因此您只需提供空映射作为配置即可。以下示例使用 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
另请参见

您可以使用格式化程序模板更改打印到日志中的内容

$ 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 宏,则会自动添加文件和行号。

另请参见

我们不是将日志打印到标准输出,而是将它们打印到旋转文件日志中。

$ 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
另请参见

添加一个处理程序,该处理程序将 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

重要的是,在调整默认处理程序的级别之前不要提升主要日志级别,否则您的标准输出可能会被调试日志消息淹没。

另请参见

记录某些内容的最简单方法是使用日志记录器宏,并向宏提供一个报告。例如,如果您想记录错误

?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 函数,其中第二个参数是格式化选项。

另请参见

过滤器用于在日志事件到达处理程序之前删除或更改它们。

如果我们只想记录特定进程的调试消息,可以使用这样的过滤器来实现

%% 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 通过的过滤器就很简单了。

请注意,通过过滤器而不是通过级别进行主要日志级别过滤要昂贵得多,因此请确保在生产节点上启用它之前测试您的系统是否可以处理额外的负载。

另请参见

域用于指定特定日志事件源自哪个子系统。默认处理程序默认情况下只记录带有 [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
另请参见