Lager (as in the beer) is a logging framework for Erlang. Its purpose is
to provide a more traditional way to perform logging in an erlang application
that plays nicely with traditional UNIX logging tools like logrotate and
syslog.
Logger calls are transformed using a parse transform to allow capturing
Module/Function/Line/Pid information
When no handler is consuming a log level (eg. debug) no event is even sent
to the log handler
Supports multiple backends, including console and file.
Rewrites common OTP error messages into more readable messages
Support for pretty printing records encountered at compile time
Tolerant in the face of large or many log messages, won’t out of memory the node
Supports internal time and date based rotation, as well as external rotation tools
Syslog style log level comparison flags
Colored terminal output (requires R16+)
Usage
To use lager in your application, you need to define it as a rebar dep or have
some other way of including it in erlang’s path. You can then add the
following option to the erlang compiler flags
{parse_transform, lager_transform}
Alternately, you can add it to the module you wish to compile with logging
enabled:
-compile([{parse_transform, lager_transform}]).
Before logging any messages, you’ll need to start the lager application. The
lager module’s start function takes care of loading and starting any dependencies
lager requires.
lager:start().
You can also start lager on startup with a switch to erl:
erl -pa path/to/lager/ebin -s lager
Once you have built your code with lager and started the lager application,
you can then generate log messages by doing the following:
lager:error("Some message")
Or:
lager:warning("Some message with a term: ~p", [Term])
The general form is lager:Severity() where Severity is one of the log levels
mentioned above.
Configuration
To configure lager’s backends, you use an application variable (probably in
your app.config):
The available configuration options for each backend are listed in their
module’s documentation.
Custom Formatting
All loggers have a default formatting that can be overriden. A formatter is any module that
exports format(#lager_log_message{},Config#any()). It is specified as part of the configuration
for the backend:
Included is lager_default_formatter. This provides a generic, default formatting for log messages using a “semi-iolist”
as configuration. Any iolist allowed elements in the configuration are printed verbatim. Atoms in the configuration
are treated as metadata properties and extracted from the log message.
The metadata properties date,time, message, and severity will always exist.
The properties pid, file, line, module, function, and node will always exist if the parser transform is used.
["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"
Optionally, a tuple of {atom(),semi-iolist()}
can be used. The atom will look up the property, but if not found it will use the semi-iolist() instead. These fallbacks
can be nested or refer to other properties.
[{pid,"Unknown Pid"}] -> "<?.?.?>" if pid is in the metadata, "Unknown Pid" if not.
[{server,[$(,{pid,"Unknown Server"},$)]}}] -> user provided server metadata, otherwise "(<?.?.?>)", otherwise "(Unknown Server)"
Error logger integration
Lager is also supplied with a error_logger handler module that translates
traditional erlang error messages into a friendlier format and sends them into
lager itself to be treated like a regular lager log call. To disable this, set
the lager application variable error_logger_redirect to false.
The error_logger handler will also log more complete error messages (protected
with use of trunc_io) to a “crash log” which can be referred to for further
information. The location of the crash log can be specified by the crash_log
application variable. If set to undefined it is not written at all.
Messages in the crash log are subject to a maximum message size which can be
specified via the crash_log_msg_size application variable.
Overload Protection
Prior to lager 2.0, the gen_event at the core of lager operated purely in
synchronous mode. Asynchronous mode is faster, but has no protection against
message queue overload. In lager 2.0, the gen_event takes a hybrid approach. it
polls its own mailbox size and toggles the messaging between synchronous and
asynchronous depending on mailbox size.
This will use async messaging until the mailbox exceeds 20 messages, at which
point synchronous messaging will be used, and switch back to asynchronous, when
size reduces to 20 - 5 = 15.
If you wish to disable this behaviour, simply set it to ‘undefined’. It defaults
to a low number to prevent the mailbox growing rapidly beyond the limit and causing
problems. In general, lager should process messages as fast as they come in, so getting
20 behind should be relatively exceptional anyway.
If you want to limit the number of messages per second allowed from error_logger,
which is a good idea if you want to weather a flood of messages when lots of
related processes crash, you can set a limit:
{error_logger_hwm, 50}
It is probably best to keep this number small.
Runtime loglevel changes
You can change the log level of any lager backend at runtime by doing the
following:
lager:set_loglevel(lager_console_backend, debug).
Or, for the backend with multiple handles (files, mainly):
Lager keeps track of the minium log level being used by any backend and
supresses generation of messages lower than that level. This means that debug
log messages, when no backend is consuming debug messages, are effectively
free. A simple benchmark of doing 1 million debug log messages while the
minimum threshold was above that takes less than half a second.
Syslog style loglevel comparison flags
In addition to the regular log level names, you can also do finer grained masking
of what you want to log:
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
These can be used anywhere a loglevel is supplied, although they need to be either
a quoted atom or a string.
Internal log rotation
Lager can rotate its own logs or have it done via an external process. To
use internal rotation, use the ‘size’, ‘date’ and ‘count’ values in the file
backend’s config:
This tells lager to log error and above messages to “error.log” and to
rotate the file at midnight or when it reaches 10mb, whichever comes first
and to keep 5 rotated logs, in addition to the current one. Setting the
count to 0 does not disable rotation, it instead rotates the file and keeps
no previous versions around. To disable rotation set the size to 0 and the
date to “”.
The “$D0” syntax is taken from the syntax newsyslog uses in newsyslog.conf.
The relevant extract follows:
Day, week and month time format: The lead-in character
for day, week and month specification is a `
Overview
Lager (as in the beer) is a logging framework for Erlang. Its purpose is to provide a more traditional way to perform logging in an erlang application that plays nicely with traditional UNIX logging tools like logrotate and syslog.
Travis-CI ::
Features
Usage
To use lager in your application, you need to define it as a rebar dep or have some other way of including it in erlang’s path. You can then add the following option to the erlang compiler flags
Alternately, you can add it to the module you wish to compile with logging enabled:
Before logging any messages, you’ll need to start the lager application. The lager module’s start function takes care of loading and starting any dependencies lager requires.
You can also start lager on startup with a switch to
erl:Once you have built your code with lager and started the lager application, you can then generate log messages by doing the following:
Or:
The general form is lager:Severity() where Severity is one of the log levels mentioned above.
Configuration
To configure lager’s backends, you use an application variable (probably in your app.config):
The available configuration options for each backend are listed in their module’s documentation.
Custom Formatting
All loggers have a default formatting that can be overriden. A formatter is any module that exports format(#lager_log_message{},Config#any()). It is specified as part of the configuration for the backend:
Included is lager_default_formatter. This provides a generic, default formatting for log messages using a “semi-iolist” as configuration. Any iolist allowed elements in the configuration are printed verbatim. Atoms in the configuration are treated as metadata properties and extracted from the log message. The metadata properties date,time, message, and severity will always exist. The properties pid, file, line, module, function, and node will always exist if the parser transform is used.
Optionally, a tuple of {atom(),semi-iolist()} can be used. The atom will look up the property, but if not found it will use the semi-iolist() instead. These fallbacks can be nested or refer to other properties.
Error logger integration
Lager is also supplied with a error_logger handler module that translates traditional erlang error messages into a friendlier format and sends them into lager itself to be treated like a regular lager log call. To disable this, set the lager application variable
error_logger_redirecttofalse.The error_logger handler will also log more complete error messages (protected with use of trunc_io) to a “crash log” which can be referred to for further information. The location of the crash log can be specified by the crash_log application variable. If set to
undefinedit is not written at all.Messages in the crash log are subject to a maximum message size which can be specified via the crash_log_msg_size application variable.
Overload Protection
Prior to lager 2.0, the gen_event at the core of lager operated purely in synchronous mode. Asynchronous mode is faster, but has no protection against message queue overload. In lager 2.0, the gen_event takes a hybrid approach. it polls its own mailbox size and toggles the messaging between synchronous and asynchronous depending on mailbox size.
This will use async messaging until the mailbox exceeds 20 messages, at which point synchronous messaging will be used, and switch back to asynchronous, when size reduces to
20 - 5 = 15.If you wish to disable this behaviour, simply set it to ‘undefined’. It defaults to a low number to prevent the mailbox growing rapidly beyond the limit and causing problems. In general, lager should process messages as fast as they come in, so getting 20 behind should be relatively exceptional anyway.
If you want to limit the number of messages per second allowed from error_logger, which is a good idea if you want to weather a flood of messages when lots of related processes crash, you can set a limit:
It is probably best to keep this number small.
Runtime loglevel changes
You can change the log level of any lager backend at runtime by doing the following:
Or, for the backend with multiple handles (files, mainly):
Lager keeps track of the minium log level being used by any backend and supresses generation of messages lower than that level. This means that debug log messages, when no backend is consuming debug messages, are effectively free. A simple benchmark of doing 1 million debug log messages while the minimum threshold was above that takes less than half a second.
Syslog style loglevel comparison flags
In addition to the regular log level names, you can also do finer grained masking of what you want to log:
These can be used anywhere a loglevel is supplied, although they need to be either a quoted atom or a string.
Internal log rotation
Lager can rotate its own logs or have it done via an external process. To use internal rotation, use the ‘size’, ‘date’ and ‘count’ values in the file backend’s config:
This tells lager to log error and above messages to “error.log” and to rotate the file at midnight or when it reaches 10mb, whichever comes first and to keep 5 rotated logs, in addition to the current one. Setting the count to 0 does not disable rotation, it instead rotates the file and keeps no previous versions around. To disable rotation set the size to 0 and the date to “”.
The “$D0” syntax is taken from the syntax newsyslog uses in newsyslog.conf. The relevant extract follows: