blob: ad7aff39a7c0ee14e0acace4f4cb87a21dc65823 [file] [log] [blame]
* 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. You can view the Lager EDocs [[][here]].
[[][Travis-CI]] :: [[]]
* Features
- Finer grained log levels (debug, info, notice, warning, error, critical,
alert, emergency)
- 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. More are planned.
* 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
-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.
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")
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):
{lager, [
{handlers, [
{lager_console_backend, info},
{lager_file_backend, [
{"error.log", error, 10485760, "$D0", 5},
{"console.log", info, 10485760, "$D0", 5}
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:
{lager, [
{handlers, [
{lager_console_backend, [info, {lager_default_formatter, [time," [",severity,"] ", message, "\n"]}},
{lager_file_backend, [
[{"error.log", error, 10485760, "$D0", 5,{lager_default_formatter,[date, " ", time," [",severity,"] ",pid, " ", message, "\n"]}],
{"console.log", info, 10485760, "$D0", 5}
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 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.
* Runtime loglevel changes
You can change the log level of any lager backend at runtime by doing the
lager:set_loglevel(lager_console_backend, debug).
Or, for the backend with multiple handles (files, mainly):
lager:set_loglevel(lager_file_backend, "console.log", debug).
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.
* Internal log rotation
Lager can rotate its own logs or have it done via an external process. To
use internal rotation, use the last 3 values in the file backend's
configuration tuple. For example
{"error.log", error, 10485760, "$D0", 5}
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 `$'-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
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
To configure the crash log rotation, the following application variables are
- crash_log_size
- crash_log_date
- crash_log_count
See the .app.src file for further details.
* Syslog Support
Lager syslog output is provided as a separate application;
[[][lager_syslog]]. It is packaged as a
separate application so Lager itself doesn't have an indirect dependancy on a
port driver. Please see the lager_syslog README for configuration information.
* AMQP Support
Jon Brisbin has written a lager backend to send lager messages into AMQP, so
you can aggregate logs from a cluster into a central point. You can find it
under the [[][lager_amqp_backend]]
project on github.
* Loggly Support
The team at [[][KIVRA]] has written a lager backend to send
lager messages into [[][Loggly]]. You can find it
under the [[][lager_loggly]]
project on github.
* SMTP Support
Ivan Blinkov has written a lager backend to send lager messages to email via SMTP,
so you can get informed about critical errors in production immediately. You can
find it under the [[][lager_smtp]] project
on github.
* Tracing
Lager supports basic support for redirecting log messages based on log message
attributes. Lager automatically captures the pid, module, function and line at the
log message callsite. However, you can add any additional attributes you wish:
lager:warning([{request, RequestID},{vhost, Vhost}], "Permission denied to ~s", [User])
Then, in addition to the default trace attributes, you'll be able to trace
based on request or vhost:
lager:trace_file("logs/", [{vhost, ""}], error)
You can also omit the final argument, and the loglevel will default to
Tracing to the console is similar:
lager:trace_console([{request, 117}])
In the above example, the loglevel is omitted, but it can be specified as the
second argument if desired.
You can also specify multiple expressions in a filter, or use the '*' atom as
a wildcard to match any message that has that attribute, regardless of its
Tracing to an existing logfile is also supported, if you wanted to log
warnings from a particular module to the default error.log:
lager:trace_file("log/error.log", [{module, mymodule}], warning)
To view the active log backends and traces, you can use the lager:status()
function. To clear all active traces, you can use lager:clear_all_traces().
To delete a specific trace, store a handle for the trace when you create it,
that you later pass to lager:stop_trace/1:
{ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]),
Tracing to a pid is somewhat of a special case, since a pid is not a
data-type that serializes well. To trace by pid, use the pid as a string:
lager:trace_console([{pid, "<0.410.0>"}])
* Setting the truncation limit at compile-time
Lager defaults to truncating messages at 4096 bytes, you can alter this by
using the {lager_truncation_size, X} option. In rebar, you can add it to
{erl_opts, [{parse_transform, lager_transform}, {lager_truncation_size, 1024}]}.
You can also pass it to erlc, if you prefer:
erlc -pa lager/ebin +'{parse_transform, lager_transform}' +'{lager_truncation_size, 1024}' file.erl