查看源码 日志记录指南

使用特别是配置 Logger 有时可能会很困难,因为有很多不同的选项可以更改,而且通常有不止一种方法可以达到相同的结果。本用户指南尝试通过提供许多不同的使用 Logger 的示例来提供帮助。

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

注意

如果您发现本指南中缺少某些常见的 Logger 用法,请在 github 上打开一个拉取请求,并提出建议的添加内容

获取 Logger 信息

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)(对于特定处理程序)获取配置。

另请参阅

配置 Logger

我的进度报告去哪了?

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

配置 Logger 格式化程序

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

单行配置

由于单行日志记录是内置格式化程序的默认设置,您只需提供空映射作为配置即可。下面的示例使用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的调用者在元数据中添加,否则 Logger 将不知道它从哪里被调用。如果您使用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

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

另请参阅

日志记录

记录什么以及如何记录

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

?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

另请参阅