Lager使用说明

概述

Lager(开源)是Erlang的日志框架。它的目的是在erlang应用程序中提供一种更传统的方式来执行日志记录,这种方式可以很好地与logrotate和syslog等传统UNIX日志记录工具兼容。

特点

  • 更细粒度的日志级别 (debug, info, notice, warning, error, critical, alert, emergency)(调试、信息、通知、警告、错误、关键、警报、紧急)
  • Logger calls是使用parse transform进行转换,以允许捕获Module/Function/Line/Pid信息
  • 当没有handler使用日志级别时(eg.debug)没有event被发送到log handler
  • 支持多个backend(后端),包括console和file
  • 支持多个sink(接收器)
  • 将常见的OTP错误消息重写为更可读的消息
  • 支持编译时遇到的漂亮打印记录
  • 容忍度在面对较大或较多的日志消息时,不会超出节点的内存
  • 绕过log size截断的可选特性(“不安全”)
  • 支持基于内部时间和日期的旋转,以及外部旋转工具
  • Syslog风格的日志级别比较标识
  • 彩色终端输出(需要R16+)
  • 支持Map (需要17+)
  • 可选的减负荷是通过设置一个高位线,在一个可配置的冷却定时器后kill(然后重新初始化) 一个sink
  • 可选的减负荷通过设置高水位线来杀死(并重新初始化)一个sink后,一个可配置的冷却定时器

贡献

我们欢迎社区的贡献。我们总是很高兴得到改进lager的想法。

如果你正在寻找一个想法来帮助解决问题,请看看我们的开放问题-有一些被标记为需要帮助和容易-还有一些两种都标记了!我们很高兴指导人们开始这些问题,他们不需要事先讨论。

也就是说,在您发送大的更改之前,请先打开一个问题来讨论您想要进行的更改以及您实现该更改的建议的想法。

PR(大概就是你要不要自己写这个功能的意思) 指导方针

  • 没有事先讨论的大改动很可能会被拒绝。
  • 没有测试用例的变更可能会被拒绝。
  • 提交PR时请使用现有代码库的风格。

我们每月至少审核一次PR和问题,如下所述。

OTP 支持策略

lager的维护者打算从当前的主3.x项目branch上支持最后的三个OTP releases。截至2019年8月,包括22、21、20
Lager可能在旧的OTP releases上运行,也可能不能,但只能保证在之前的三个OTP releases上进行测试。如果您需要运行在较旧的OTP releases上的lager版本,我们建议您使用3.4.0release或2.x branch。

每月分流节奏

我们(至少)每月都会在freenode IRC网络的#lager房间里为lager进行分类和PR,每三个星期四下午2点美国/太平洋地区,晚上10点UTC时间。欢迎您在那里加入我们,问关于lager的问题或参与分类。

使用说明

要在应用程序中使用lager,需要将其定义为一个rebar 依赖或者以其他方式将其包含在Erlang的路径中。然后可以将以下选项添加到erlang编译器标识中

{parse_transform, lager_transform}

或者,你可以把它添加到你想要编译的模块中,并启用日志记录:

-compile([{parse_transform, lager_transform}]).

在记录任何消息之前,您需要启动lager应用程序。lager模块的start函数负责加载和启动lager所需的任何依赖项.

lager:start().

你也可以通过切换到erl来启动lager:

erl -pa path/to/lager/ebin -s lager

一旦你用lager构建了你的代码并启动了lager应用程序,你就可以通过以下操作来生成日志消息:

lager:error("Some message")

或者:

lager:warning("Some message with a term: ~p", [Term])

一般形式是lager:Severity(),其中Severity是上面提到的日志级别之一。

配置

要配置lager的backends,你需要使用一个应用变量(可能在你的app.config中):

{lager, [
  {log_root, "/var/log/hello"},
  {handlers, [
    {lager_console_backend, [{level, info}]},
    {lager_file_backend, [{file, "error.log"}, {level, error}]},
    {lager_file_backend, [{file, "console.log"}, {level, info}]}
  ]}
]}.

log_root变量是可选的,默认情况下文件路径是相对于CWD的。
每个 backend可用的配置选项在各自模块的文档中列出。

Sinks(接收器)

Lager通常上支持名为lager_event的单一sink(实现为gen_event管理器),所有backend都连接到该sink。
Lager现在支持拓展的sink;每个sink可以有不同的sync/async消息threshold(阈值)和不同的backend。

Sink 配置

要使用多个sink(除了内置的lager和lager_event接收器),你需要:

  1. 配置 rebar.config
  2. 配置backend在app.config中

名称

每个sink有两个名称:一个atom用作发送消息的模块名称,另一个atom++"_lager_event"用于backend配置。

这反映了遗留的行为模式:lager:info(或critical,或debug等)是向被命名为为lager_event的sink发送消息的一种方式。现在,只要配置了相应的audit_lager_event或myCompanyName_lager_event接收器,开发人员就可以调用audit:info或myCompanyName:debug。

rebar.config

需要用到lager的项目的rebar.config中,包括在erl_opts中的sink名称列表(不带_lager_event后缀): 

{lager_extra_sinks, [audit]}

运行要求

为了起作用, sink必须在运行时使用backend进行配置。

需要用到lager的项目的app.config中, 例如,扩展lager配置以包含带有backends(即“handlers”)的extra_sink元组,以及可选的async_threshold和async_threshold_window值(参见下面的过载保护)。如果未配置异步值,则不会在该sink上应用过载保护。

[{lager, [
          {log_root, "/tmp"},

          %% Default handlers for lager/lager_event
          {handlers, [
                      {lager_console_backend, [{level, info}]},
                      {lager_file_backend, [{file, "error.log"}, {level, error}]},
                      {lager_file_backend, [{file, "console.log"}, {level, info}]}
                     ]},

          %% Any other sinks
          {extra_sinks,
           [
            {audit_lager_event,
             [{handlers,
               [{lager_file_backend,
                 [{file, "sink1.log"},
                  {level, info}
                 ]
                }]
              },
              {async_threshold, 500},
              {async_threshold_window, 50}]
            }]
          }
         ]
 }
].

自定义格式

所有logger都有一个可以被覆盖的默认格式。格式化器是任何导出格式的模块(#lager_log_message{},Config#any())。它被指定为backend配置的一部分:

{lager, [
  {handlers, [
    {lager_console_backend, [{level, info}, {formatter, lager_default_formatter},
      {formatter_config, [time," [",severity,"] ", message, "\n"]}]},
    {lager_file_backend, [{file, "error.log"}, {level, error}, {formatter, lager_default_formatter},
      {formatter_config, [date, " ", time," [",severity,"] ",pid, " ", message, "\n"]}]},
    {lager_file_backend, [{file, "console.log"}, {level, info}]}
  ]}
]}.

包括lager_default_formatter。它为日志消息提供了一种通用的默认格式,使用类似于Erlang的iolist结构,我们称之为“semi-iolist”:

  • 配置中的任何传统ilist元素都是逐字打印的.
  • 配置中的atom被视为lager元数据的占位符,并从日志消息中提取
  • 占位符datetimemessage、sev和severity 将始终存在.
  • sev是严重性级别的缩写,解释为严重性级别的大写字母编码 (e.g. 'debug' -> $D).
  • 如果使用parse transform,占位符pid、file、line、module、function和node将始终存在
  • 如果使用parse transform,占位符应用程序可能存在。它依赖于找到应用程序的app.src文件.
  • 如果使用error logger集成,则占位符pid将始终存在,占位符name也可能存在
  • 应用程序可以定义自己的元数据占位符.
  • 由{atom(), semi-iolist()}组成的元组允许回退atom占位符。如果无法找到由原子表示的值,semi-iolist则将解释.
  • 一个由{atom(), semi-iolist(), semi-iolist()}组成的元组表示一个条件操作符:如果可以找到原子占位符的值,则输出第一个semi-iolist;否则,将使用第二个.
  • 一个由{pterm, atom()}组成的元组将尝试从OTP 21.2中添加的persistent_term特性中查找指定原子的值。默认值为“”。如到key,或者在OTP 21之前的OTP版本中指定了该格式项,则使用默认值。
  • 一个由{pterm, atom(), semi-iolist()}组成的元组将尝试从OTP 21.2中添加的persistent_term特性中查找指定原子的值。默认值是指定的semi-iolist()。如果找不到key,或者在OTP 21之前的OTP版本中指定了该格式项,则使用默认值.

例如:

["Foo"] -> "Foo", regardless of message content.
[message] -> The content of the logged message, alone.
[{pid,"Unknown Pid"}] -> "<?.?.?>" if pid is in the metadata, "Unknown Pid" if not.
[{pid, ["My pid is ", pid], ["Unknown Pid"]}] -> if pid is in the metadata print "My pid is <?.?.?>", otherwise print "Unknown Pid"
[{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] -> user provided server metadata, otherwise "(<?.?.?>)", otherwise "(Unknown Server)"
[{pterm, pterm_key, <<"undefined">>}] -> if a value for 'pterm_key' is found in OTP 21 (or later) persistent_term storage it is used, otherwise "undefined"

世界时间

通常,lager将时间戳格式为生成日志消息的计算机的本地时间。

要使lager使用UTC时间戳,您可以在您的应用程序配置文件中将sasl应用程序的utc_log配置参数设置为true。

举例:

%% format log timestamps as UTC
[{sasl, [{utc_log, true}]}].

错误日志记录器集成

错误日志记录器集成

Lager还提供了一个error_logger处理程序模块,该模块将传统的erlang错误消息转换为更友好的格式,并将它们发送到Lager本身,以像常规的Lager日志调用一样对待它们。要禁用此功能,请将大型应用程序变量error_logger_redirect设置为false。您还可以通过将变量error_logger_format_raw设置为true来禁用OTP和Cowboy消息的重新格式化。

如果您将自己的handler安装到error_logger中,您可以通过使用error_logger_whitelist环境变量和一个允许的处理程序列表来告诉lager不要去管它。

{error_logger_whitelist, [my_handler]}

error_logger处理程序还会将更完整的错误消息(使用trunc_io进行保护)记录到“crash log”中,可以参考该日志获取更多信息。crash log的位置可以由crash_log应用程序变量指定。如果设置为false,则完全不写入。

crash log中的消息受最大消息大小的影响,可以通过应用程序变量crash_log_msg_size指定该消息大小。

来自error_logger的消息将被重定向到error_logger_lager_event sink(如果定义了error_logger_lager_event sink,那么它可以被重定向到另一个日志文件)。

例如:

[{lager, [
         {extra_sinks,
          [
           {error_logger_lager_event, 
            [{handlers, [
              {lager_file_backend, [{file, "error_logger.log"}, {level, info}]}]
              }]
           }]
           }]
}].

将发送所有error_logger消息到error_logger.log文件.

超载防护

异步方式

在lager 2.0之前,lager核心的gen_event完全以同步模式运行。异步模式更快,但没有针对消息队列过载的保护。在lager 2.0中,gen_event采用混合方法。它轮询自己的邮箱大小,并根据邮箱大小在同步和异步之间切换消息传递。

{async_threshold,20},

{async_threshold_window,5}

这将使用异步消息传递,直到邮箱超过20条消息,此时将使用同步消息传递,并在大小减少到20 - 5 = 15时切换回异步消息传递。

如果您希望禁用此行为,只需将async_threshold设置为undefined。它默认设置一个较低的数字,以防止邮箱快速增长,超出限制并导致问题。一般来说,Lager处理消息的速度应该和收到消息的速度一样快,所以落后20个消息应该是相对特殊的。

如果你想限制error_logger每秒允许的消息数量,这是一个好主意,如果你想在许多相关进程崩溃时经受住消息泛滥,你可以设置一个限制:

{error_logger_hwm, 50}

也许最好保持这个数字较小。

事件队列刷新

当超过高位标记时,可以将lager配置为刷新消息队列中的所有事件通知。这可能会给同一事件管理器中的其他处理程序(例如error_logger)带来意想不到的后果,因为它们所依赖的事件可能会被错误地丢弃。默认情况下,这个行为是启用的,但是可以通过以下方式控制error_logger:

{error_logger_flush_queue, true | false}

或者对于特定的skin,使用选项:

{flush_queue, true | false}

如果flush_queue为true,可以设置消息队列长度阈值,在该阈值下消息将开始被丢弃。默认阈值为0,这意味着如果flush_queue为true,则无论消息队列的长度如何,如果超过了高位标记,消息将被丢弃。对于error_logger,控制阈值的选项是:

{error_logger_flush_threshold, 1000}

和 对于sink:

{flush_threshold, 1000}

Sink Killer

在一些高容量的情况下,最好丢弃所有挂起的日志消息,而不是让它们随着时间的推移而流失。

如果您愿意,您可以选择使用sink killer去卸载。在此操作模式下,如果gen_event邮箱超过了可配置的高位标志,sink将在可配置的冷却时间之后被kill并重新安装。

你可以使用下面的配置指令来配置这个行为:

{killer_hwm, 1000},
{killer_reinstall_after, 5000}

这意味着如果接收器的邮箱大小超过1000条消息,则杀死整个sink并在5000毫秒后重新加载它。如果需要,还可以将此行为安装到其他sink中。

默认情况下,manager killer不会安装到任何sink中。如果killer_reinstall_after冷却时间没有指定,则默认为5000。

"Unsafe"

不安全的代码路径绕过了正常的大容量格式化代码,并使用与OTP中的error_logger相同的代码。这为您的日志代码提供了一个边际的加速(在我们的基准测试期间,我们测量了0.5-1.3%的改进;其他人报告说情况有所改善。)

这是一个危险的特性。它不会保护您免受大日志消息的影响——大消息可能会由于内存耗尽而杀死您的应用程序,甚至您的Erlang VM,因为在失败级联中会反复复制大的条目。我们强烈建议这种代码路径只用于有良好边界的日志消息,其上限约为500字节。

如果日志消息有可能超过这个限制,那么应该使用正常的大型消息格式化代码,这将提供适当的大小限制并防止内存耗尽。

如果您想格式化不安全的日志消息,您可以使用严重性级别(通常)后跟_unsafe。这里有一个例子:

lager:info_unsafe("The quick brown ~s jumped over the lazy ~s", ["fox", "dog"]).

运行时loglevel变化

您可以在运行时通过以下操作来更改任何lager backend的日志级别:

lager:set_loglevel(lager_console_backend, debug).

Or, for the backend with multiple handles (files, mainly):

或者,对于具有多个handle(主要是file)的backend:

lager:set_loglevel(lager_file_backend, "console.log", debug).

Lager跟踪任何backend使用的最小日志级别,并抑制生成低于该级别的消息。这意味着,当没有backend使用调试消息时,调试日志消息实际上是空闲的。当最小阈值超过100万条调试日志消息时,一个简单的基准测试花费不到半秒的时间.

Syslog风格的日志级别比较标志

除了常规的日志级别名称,你还可以做更细粒度的屏蔽你想要记录的内容:

info - info and higher (>= is implicit)
=debug - only the debug level
!=info - everything but the info level
<=notice - notice and below
<warning - anything less than warning

它们可以在提供日志级别的任何地方使用,尽管它们需要是引用的原子或字符串。

内部日志轮询

Lager可以轮询自己的日志,也可以通过外部进程来完成。要使用内部轮询,使用file backend配置中的size, date和count值:

[{file, "error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}]

这告诉lager将error和以上消息记录到error.log,并在午夜或文件达到10mb时轮询该文件(以先到者为准),并且在当前的日志之外保留5个轮询日志。将计数设置为0并不禁用轮询,相反,它会轮询文件并保留以前的版本。要禁用轮询需要设置size为0和date为“”。

$D0语法来自newsyslog在newsyslog.conf中使用的语法。相关摘录如下:

Day, week and month time format: The lead-in character
for day, week and month specification is a `$'-sign.
The particular format of day, week and month
specification is: [Dhh], [Ww[Dhh]] and [Mdd[Dhh]],
respectively.  Optional time fields default to
midnight.  The ranges for day and hour specifications
are:

  hh      hours, range 0 ... 23
  w       day of week, range 0 ... 6, 0 = Sunday
  dd      day of month, range 1 ... 31, or the
          letter L or l to specify the last day of
          the month.

Some examples:
  $D0     rotate every night at midnight
  $D23    rotate every day at 23:00 hr
  $W0D23  rotate every week on Sunday at 23:00 hr
  $W5D16  rotate every week on Friday at 16:00 hr
  $M1D0   rotate on the first day of every month at
          midnight (i.e., the start of the day)
  $M5D6   rotate on every 5th day of the month at
          6:00 hr

除了从newsyslog中获得的日、周和月时间格式外,还从PR #420中添加了小时规范

Format of hour specification is : [Hmm]
The range for minute specification is:

  mm      minutes, range 0 ... 59

Some examples:

  $H00    rotate every hour at HH:00
  $D12H30 rotate every day at 12:30
  $W0D0H0 rotate every week on Sunday at 00:00

要配置崩溃日志轮询,需要使用以下应用程序变量:

  • crash_log_size
  • crash_log_date
  • crash_log_count
  • crash_log_rotator

看看 .app.src以获得更多细节.

自定义日志轮询

自定义日志轮询可以配置为lager_file_backend选项

{rotator, lager_rotator_default}

模块应该提供以下回调函数作为lager_rotator_behaviour

%% @doc Create a log file
-callback(create_logfile(Name::list(), Buffer::{integer(), integer()} | any()) ->
    {ok, {FD::file:io_device(), Inode::integer(), Size::integer()}} | {error, any()}).

%% @doc Open a log file
-callback(open_logfile(Name::list(), Buffer::{integer(), integer()} | any()) ->
    {ok, {FD::file:io_device(), Inode::integer(), Size::integer()}} | {error, any()}).

%% @doc Ensure reference to current target, could be rotated
-callback(ensure_logfile(Name::list(), FD::file:io_device(), Inode::integer(),
                         Buffer::{integer(), integer()} | any()) ->
    {ok, {FD::file:io_device(), Inode::integer(), Size::integer()}} | {error, any()}).

%% @doc Rotate the log file
-callback(rotate_logfile(Name::list(), Count::integer()) ->
    ok).

Syslog 支持

Lager的syslog输出作为一个单独的应用程序提供:lager_syslog。它被打包为一个单独的应用程序,因此lager本身不会间接依赖于端口驱动程序。有关配置信息,请参阅lager_syslog README。

其他Backend

他们有很多!有些将日志消息连接到AMQP、各种日志分析服务(bunyan、loggly等)等。寻找十六进制或使用“lager BACKEND”,“BACKEND”是你最喜欢的日志解决方案在你最喜欢的搜索引擎是一个很好的起点。

漂亮的异常打印

升级到 OTP 20:

try
    foo()
catch
    Class:Reason ->
        lager:error(
            "~nStacktrace:~s",
            [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})])
end.

在 OTP 21+:

try
    foo()
catch
    Class:Reason:Stacktrace ->
        lager:error(
            "~nStacktrace:~s",
            [lager:pr_stacktrace(Stacktrace, {Class, Reason})])
end.

漂亮的Record打印

Lager的parse transform将跟踪它遇到的任何record定义,并将它们存储在模块的属性中。然后,在运行时,通过使用lager:pr/2函数,打印使用lager parse transform编译的模块知道的任何记录,该函数获取该记录和知道该记录的模块:

lager:info("My state is ~p", [lager:pr(State, ?MODULE)])

通常,?MODULE就足够了,但显然您可以将其替换为一个字面模块名。lager:pr也从shell上起作用。

彩色的终端输出

如果您有Erlang R16或更高版本,您可以告诉lager的console backend是彩色的。简单地添加到lager的应用程序环境配置:

{colored, true}

如果你不喜欢默认颜色,它们也是可配置的;看到.app.src文件获取更多细节。

输出将从格式化配置中第一次出现的atom颜色开始着色。例如:

{lager_console_backend, [{level, info}, {formatter, lager_default_formatter},
  {formatter_config, [time, color, " [",severity,"] ", message, "\e[0m\r\n"]}]]}

这将使除时间外的整个日志消息都变成彩色。为了在每个日志消息后重置颜色,需要换行符之前的转义序列。

追溯

Lager支持基于日志消息属性重定向日志消息的基本支持。Lager自动捕获pid,module, function,和line在日志消息的callsite。但是,你可以添加任何你想要的附加属性:

lager:warning([{request, RequestID},{vhost, Vhost}], "Permission denied to ~s", [User])

然后,除了默认的跟踪属性,您将能够基于request或vhost追溯:

lager:trace_file("logs/example.com.error", [{vhost, "example.com"}], error)

要在进程的生命周期内持久化元数据,可以使用lager:md/1将元数据存储在进程字典中:

lager:md([{zone, forbidden}])

注意,lager:md只接受由atom组成的key/value对列表。

您也可以省略最后一个参数,并且日志级别将默认为debug。

对控制台的跟踪是类似的:

lager:trace_console([{request, 117}])

在上面的示例中,忽略了loglevel,但是如果需要,可以将它指定为第二个参数。

您还可以在过滤器中指定多个表达式,或者使用*原子作为通配符来匹配任何具有该属性的消息,而不管其值如何。您也可以使用特殊值!也就是说,只有在不存在此键时才进行选择。

也支持跟踪到现有的日志文件(但参见下面的多个sink支持):

lager:trace_file("log/error.log", [{module, mymodule}, {function, myfunction}], warning)

要查看活动日志backend和跟踪信息,可以使用lager:status()函数。要清除所有活动跟踪,可以使用lager:clear_all_traces()。

要删除特定的跟踪,请在创建跟踪时存储追溯的handle,稍后将其传递给lager:stop_trace/1:

{ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]),
...
lager:stop_trace(Trace)

跟踪pid是一种特殊的情况,因为pid不是一种序列化良好的数据类型。使用pid作为字符串来跟踪:

lager:trace_console([{pid, "<0.410.0>"}])

过滤器表达式

从lager 3.3.1开始,在跟踪第二个元素是比较操作符的位置时,还可以使用3元组。目前支持的比较操作符有:

  • < - less than
  • =< - less than or equal
  • = - equal to
  • != - not equal to
  • > - greater than
  • >= - greater than or equal
lager:trace_console([{request, '>', 117}, {request, '<', 120}])

使用=等同于2元组形式。

过滤器组成

在lager 3.3.1中,您还可以使用all或any的特殊过滤组合键。例如,上面的过滤器示例可以表示为:

lager:trace_console([{all, [{request, '>', 117}, {request, '<', 120}]}])

any在过滤器之间具有“OR style”的逻辑评估效果;all表示过滤器之间的“AND style”逻辑计算。这些组合过滤器需要一个附加过滤器表达式列表作为它们的值。

Null过滤器

null过滤器有特殊的含义。{null, false}的过滤器就像一个黑洞;没有东西通过。一个{null, true}的过滤器意味着所有东西都通过。null过滤器的其他值都无效,将被拒绝。

Silence过滤器

silence是一种特殊的日志级别,可以与过滤器一起使用,以抑制特定的日志输出。如果backend已经为特定的日志级别配置了,但特定的日志消息集使日志混乱,那么这可能很有用。如果这些依赖项来自于依赖项,则可能很难完全删除它们,通常也不希望这样做。在这种情况下,带有日志级别silence的跟踪过滤器可以选择性地关闭它们,同时让其他消息像以前一样通过。

多sink支持

如果使用多个sink,您应该注意跟踪方面的限制。

跟踪是特定于sink的,可以通过跟踪过滤器指定:

lager:trace_file("log/security.log", [{sink, audit_event}, {function, myfunction}], warning)

如果没有指定sink,那么将使用默认的lager sink。

这有两个后果:

  • 跟踪不能拦截发送到不同sink的消息。
  • 跟踪一个已经通过lager:trace_file打开的文件,只有在指定相同的sink时才会成功。

前者可以通过开多个跟踪来改善;后者可以通过重新设计lager的file backend来修复,但这还没有解决。

从配置追踪

Lager支持从其配置文件开始跟踪。定义它们的关键字是trace,然后是元组的proplist(该元组定义一个backend handler)和一个必需列表中的零个或多个过滤器,然后是可选的消息severity级别。

一个例子是这样的:

{lager, [
  {handlers, [...]},
  {traces, [
    %% handler,                         filter,                message level (defaults to debug if not given)
    {lager_console_backend,             [{module, foo}],       info },
    {{lager_file_backend, "trace.log"}, [{request, '>', 120}], error},
    {{lager_file_backend, "event.log"}, [{module, bar}]             } %% implied debug level here
  ]}
]}.

在本例中,我们有三个跟踪。一个使用console backend,两个使用file backend。如果忽略了消息的严重性级别,则默认为debug,就像上一个file backend示例中那样。

traces 关键字也适用于可选的sink,但同样的限制和上述注意到的警告适用。

重要提示:您必须在3.1.0或之前的所有lager版本中定义一个severity级别。2元组形式直到3.2.0才添加。

在编译时设置动态元数据

Lager支持通过注册回调函数从外部来源提供元数据。即使进程死亡,该元数据也可以跨进程持久存在。

在一般情况下,您不需要使用此功能。然而,它在以下情况下是有用的:

  • 由seq_trace提供的跟踪信息
  • 关于应用程序的上下文信息
  • 默认占位符不提供的持久信息
  • 需要在每次日志调用之前设置元数据的情况

你可以使用{lager_parse_transform_functions, X}选项来添加回调。它仅在使用parse_transform时可用。在rebar中,你可以将其添加到erl_opts中,如下所示:

{erl_opts, [{parse_transform, lager_transform}, 
            {lager_function_transforms, 
              [
                 %% Placeholder              Resolve type  Callback tuple
                {metadata_placeholder,       on_emit,      {module_name, function_name}},
                {other_metadata_placeholder, on_log,       {module_name, function_name}}
              ]}]}.

第一个原子是自定义格式化程序中用于替换的占位符原子。有关更多信息,请参阅自定义格式。

第二个原子是解析类型。这将指定在发出消息时或在记录调用时要解析的回调。您必须指定原子on_emit或on_log。没有一个“正确的”解析类型可以使用,所以请阅读每一个的用法/说明,并选择最适合您的要求的选项。

on_emit:

  • 直到backend发出消息,回调函数才被解析。
  • 如果回调函数无法解析、未加载或产生未处理的错误,则返回undefined。
  • 由于回调函数依赖于进程,因此有可能在依赖的进程死亡后发出消息,从而返回undefined。这个过程也可以是您自己的过程

on_log:

  • 无论消息是否正确发出,回调函数都会解析
  • 如果回调函数不能解析或不能加载,则lager本身不会处理错误。
  • 回调中任何潜在的错误都应该在回调函数本身中处理。
  • 因为函数是在日志时间解析的,所以在你解决它之前,依赖进程死亡的可能性应该更小,特别是当你从包含回调的应用程序中进行日志记录时。

第三个元素是你的函数的回调函数,它由一个形式为{Module function}的元组组成。无论使用的是on_emit还是on_log,回调函数应该如下所示:

  • 它应该被export
  • 它不应该接受任何参数,例如它的单位是0
  • 它应该返回任何传统的ioliist元素或undefined
  • 有关在回调中生成的错误,请参阅上面的解析类型文档。

如果回调函数返回undefined,那么它将遵循与自定义格式小节中描述的相同的回退和条件操作符规则。

这个示例可以与on_emit一起工作,但与on_log一起使用可能不安全。如果在on_emit中调用失败,它将默认为undefined,而在on_log中则会出错。

-export([my_callback/0]).

my_callback() ->
  my_app_serv:call('some options').

这个例子对于同时使用on_emit和on_log来说是安全的

-export([my_callback/0]).

my_callback() ->
  try my_app_serv:call('some options') of
    Result ->
      Result
  catch
    _ ->
      %% You could define any traditional iolist elements you wanted here
      undefined
  end.

注意回调函数可以是任何Module:Function/0。它不是你的应用程序的一部分。例如,你可以使用cpu_sup:avg1/0作为你的回调函数{cpu_avg1, on_emit, {cpu_sup, avg1}}

例子:

-export([reductions/0]).

reductions() ->
  proplists:get_value(reductions, erlang:process_info(self())).
-export([seq_trace/0]).

seq_trace() ->
  case seq_trace:get_token(label) of
    {label, TraceLabel} ->
      TraceLabel;
    _ ->
      undefined
  end.

重要提示:由于on_emit依赖于在发出日志消息时注入的函数调用,因此您的日志记录性能(ops/sec)将受到您调用的函数所做的事情以及它们可能带来的延迟的影响。对于on_log,这种影响会更大,因为调用是在记录消息时注入的。

在编译时设置截断限制

Lager默认将消息截断为4096字节,你可以使用{lager_truncation_size, X}选项来改变这一点。在rebar中,你可以将它添加到erl_opts:

{erl_opts, [{parse_transform, lager_transform}, {lager_truncation_size, 1024}]}.

你也可以把它传递给erlc,如果你喜欢的话:

erlc -pa lager/ebin +'{parse_transform, lager_transform}' +'{lager_truncation_size, 1024}' file.erl

抑制应用程序和监控程序启动/停止日志

如果你不想看到监控器和应用程序在你的应用程序的调试级别启动/停止日志,你可以使用下面的配置来关闭它:

{lager, [{suppress_application_start_stop, true},
         {suppress_supervisor_start_stop, true}]}

系统调试功能

Lager提供了一种使用sys“debug functions”的集成方式。执行以下操作,可以在目标进程中安装调试函数

lager:install_trace(Pid, notice).

你也可以自定义跟踪:

lager:install_trace(Pid, notice, [{count, 100}, {timeout, 5000}, {format_string, "my trace event ~p ~p"]}).

跟踪选项目前是:

  • timeout - 跟踪安装的时间:infinity (默认)或毫秒超时
  • count - 要记录多少跟踪事件:infinity (默认)还是正数
  • format_string - 用于记录事件的格式字符串。必须为提供的2个参数提供2个格式说明符.

这将在OTP进程的每个“system event”(通常是入站消息、回复和状态更改)上生成指定日志级别的lager消息。

当你完成跟踪时,你可以这样做:

lager:remove_trace(Pid).

如果你想从一开始就启用跟踪来启动一个OTP进程,你可以这样做:

gen_server:start_link(mymodule, [], [{debug, [{install, {fun lager:trace_func/3, lager:trace_state(undefined, notice, [])}}]}]).

trace_state函数的第三个参数是上面记录的Option列表。

Console输出到另一个组leader进程

如果您想将控制台输出发送到另一个group_leader(通常在另一个节点上),您可以为console backend提供{group_leader, Pid}参数。这可以与另一个控制台配置选项id和gen_event的{Module, id}相结合,以允许远程跟踪一个节点,通过nodetool标准输出:

    GL = erlang:group_leader(),
    Node = node(GL),
    lager_app:start_handler(lager_event, {lager_console_backend, Node},
         [{group_leader, GL}, {level, none}, {id, {lager_console_backend, Node}}]),
    case lager:trace({lager_console_backend, Node}, Filter, Level) of
         ...

在上面的示例中,假定代码通过一个nodetool rpc调用运行,因此代码在Erlang节点上执行,但是group_leader是reltool节点(eg.appname_maint_12345@127.0.0.1)。

如果您打算使用此特性进行跟踪,请确保start_handler的第二个参数和id参数匹配。因此,当自定义group_leader进程退出时,lager将删除该处理程序的任何相关跟踪。

Elixir 支持

在Elixir项目中,lager有两种使用方式:

  1. 用于Elixir 日志记录的Lager Backend 
  2. 直接使用

用于Elixir 日志记录的Lager Backend 

Elixir的Logger是将日志记录添加到Elixir代码的惯用方法。Logger有一个插件模型,允许使用不同的日志backend,而不需要更改项目中的日志代码。

这种方法将受益于大多数elixir库和框架都可能使用elixir Logger,因此所有日志记录都将通过相同的日志记录机制进行。

在elixir 1.5中,已弃用对parse transform的支持。采用“Lager as a Logger Backend”的方法可能会绕过任何相关的回归问题,这些问题可能会被引入到在更新到elixir 1.5时直接使用Lager的项目中。

Lager有一些开源的elixir Logger后端:

直接使用

在elixir 1.5之前,完全可以直接使用lager及其所有功能。

在elixir 1.5之后,不支持parse transform,建议对lager api使用elixir包装器,当选择直接使用lager时,通过elixir宏提供编译时日志级别排除。

包括lager作为依赖:

# mix.exs
def application do
  [
    applications: [:lager],
    erl_opts: [parse_transform: "lager_transform"]
  ]
end

defp deps do
  [{:lager, "~> 3.2"}]
end

示例配置:

# config.exs
use Mix.Config

# Stop lager writing a crash log
config :lager, :crash_log, false

config :lager,
  log_root: '/var/log/hello',
  handlers: [
    lager_console_backend: :info,
    lager_file_backend: [file: "error.log", level: :error],
    lager_file_backend: [file: "console.log", level: :info]
  ]

有一个已知的问题,Elixir的Logger和Lager在并排使用时都会争夺Erlang error_logger handle。

如果使用两者,添加以下到你的config. exe:

# config.exs
use Mix.Config

# Stop lager redirecting :error_logger messages
config :lager, :error_logger_redirect, false

# Stop lager removing Logger's :error_logger handler
config :lager, :error_logger_whitelist, [Logger.ErrorHandler]

使用示例:

:lager.error('Some message')
:lager.warning('Some message with a term: ~p', [term])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值