SkillAgentSearch skills...

Lager

A logging framework for Erlang/OTP

Install / Use

/learn @erlang-lager/Lager
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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.

Build Status Hex pm

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 sent to the log handler
  • Supports multiple backends, including console and file.
  • Supports multiple sinks
  • 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
  • Optional feature to bypass log size truncation ("unsafe")
  • Supports internal time and date based rotation, as well as external rotation tools
  • Syslog style log level comparison flags
  • Colored terminal output (requires R16+)
  • Map support (requires 17+)
  • Optional load shedding by setting a high water mark to kill (and reinstall) a sink after a configurable cool down timer

Contributing

We welcome contributions from the community. We are always excited to get ideas for improving lager.

If you are looking for an idea to help out, please take a look at our open issues - a number of them are tagged with Help Wanted and Easy - some of them are tagged as both! We are happy to mentor people get started with any of these issues, and they don't need prior discussion.

That being said, before you send large changes please open an issue first to discuss the change you'd like to make along with an idea of your proposal to implement that change.

PR guidelines

  • Large changes without prior discussion are likely to be rejected.
  • Changes without test cases are likely to be rejected.
  • Please use the style of the existing codebase when submitting PRs.

We review PRs and issues at least once a month as described below.

OTP Support Policy

The lager maintainers intend to support the past three OTP releases from current on the main 3.x branch of the project. As of August 2019 that includes 22, 21 20

Lager may or may not run on older OTP releases but it will only be guaranteed tested on the previous three OTP releases. If you need a version of lager which runs on older OTP releases, we recommend you use either the 3.4.0 release or the 2.x branch.

Monthly triage cadence

We have (at least) monthly issue and PR triage for lager in the #lager room on the freenode IRC network every third Thursday at 2 pm US/Pacific, 10 pm UTC. You are welcome to join us there to ask questions about lager or participate in the triage.

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):

{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 variable is optional, by default file paths are relative to CWD.

The available configuration options for each backend are listed in their module's documentation.

Sinks

Lager has traditionally supported a single sink (implemented as a gen_event manager) named lager_event to which all backends were connected.

Lager now supports extra sinks; each sink can have different sync/async message thresholds and different backends.

Sink configuration

To use multiple sinks (beyond the built-in sink of lager and lager_event), you need to:

  1. Setup rebar.config
  2. Configure the backends in app.config

Names

Each sink has two names: one atom to be used like a module name for sending messages, and that atom with _lager_event appended for backend configuration.

This reflects the legacy behavior: lager:info (or critical, or debug, etc) is a way of sending a message to a sink named lager_event. Now developers can invoke audit:info or myCompanyName:debug so long as the corresponding audit_lager_event or myCompanyName_lager_event sinks are configured.

rebar.config

In rebar.config for the project that requires lager, include a list of sink names (without the _lager_event suffix) in erl_opts:

{lager_extra_sinks, [audit]}

Runtime requirements

To be useful, sinks must be configured at runtime with backends.

In app.config for the project that requires lager, for example, extend the lager configuration to include an extra_sinks tuple with backends (aka "handlers") and optionally async_threshold and async_threshold_window values (see Overload Protection below). If async values are not configured, no overload protection will be applied on that 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}]
            }]
          }
         ]
 }
].

Custom Formatting

All loggers have a default formatting that can be overridden. 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, [{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}]}
  ]}
]}.

Included is lager_default_formatter. This provides a generic, default formatting for log messages using a structure similar to Erlang's iolist which we call "semi-iolist":

  • Any traditional iolist elements in the configuration are printed verbatim.
  • Atoms in the configuration are treated as placeholders for lager metadata and extracted from the log message.
    • The placeholders date, time, message, sev and severity will always exist.
    • sev is an abbreviated severity which is interpreted as a capitalized single letter encoding of the severity level (e.g. 'debug' -> $D)
    • The placeholders pid, file, line, module, function, and node will always exist if the parse transform is used.
    • The placeholder application may exist if the parse transform is used. It is dependent on finding the applications app.src file.
    • If the error logger integration is used, the placeholder pid will always exist and the placeholder name may exist.
    • Applications can define their own metadata placeholder.
    • A tuple of {atom(), semi-iolist()} allows for a fallback for the atom placeholder. If the value represented by the atom cannot be found, the semi-iolist will be interpreted instead.
    • A tuple of {atom(), semi-iolist(), semi-iolist()} represents a conditional operator: if a value for the atom placeholder can be found, the first semi-iolist will be output; otherwise, the second will be used.
    • A tuple of {pterm, atom()} will attempt to lookup the value of the specified atom from the persistent_term feature added in OTP 21.2. The default value is "". The default value will be used if the key cannot be found or if this formatting term is specified on an OTP release before OTP 21.
    • A tuple of `{pterm, atom(), semi-iolist
View on GitHub
GitHub Stars1.1k
CategoryDevelopment
Updated14d ago
Forks447

Languages

Erlang

Security Score

100/100

Audited on Mar 13, 2026

No findings