{"id":13561991,"url":"https://github.com/erlang-lager/lager","last_synced_at":"2025-05-13T23:10:53.853Z","repository":{"id":44709234,"uuid":"1948141","full_name":"erlang-lager/lager","owner":"erlang-lager","description":"A logging framework for Erlang/OTP","archived":false,"fork":false,"pushed_at":"2024-10-15T17:04:47.000Z","size":2197,"stargazers_count":1124,"open_issues_count":52,"forks_count":454,"subscribers_count":137,"default_branch":"master","last_synced_at":"2025-04-23T15:59:57.301Z","etag":null,"topics":["erlang","logging","otp"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erlang-lager.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-06-24T15:19:23.000Z","updated_at":"2025-04-23T02:12:40.000Z","dependencies_parsed_at":"2024-01-06T12:18:44.643Z","dependency_job_id":"6f25c27b-a952-4ccf-84c1-4c06e6f5b09b","html_url":"https://github.com/erlang-lager/lager","commit_stats":{"total_commits":796,"total_committers":131,"mean_commits":6.076335877862595,"dds":0.620603015075377,"last_synced_commit":"fb340d7bbdf67c653f6c7c6cc37fbbe4fc3781c0"},"previous_names":[],"tags_count":57,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-lager%2Flager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-lager%2Flager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-lager%2Flager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erlang-lager%2Flager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erlang-lager","download_url":"https://codeload.github.com/erlang-lager/lager/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254042336,"owners_count":22004901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erlang","logging","otp"],"created_at":"2024-08-01T13:01:03.359Z","updated_at":"2025-05-13T23:10:48.828Z","avatar_url":"https://github.com/erlang-lager.png","language":"Erlang","funding_links":[],"categories":["Libraries","Erlang Tools, Libraries, and Frameworks","Tools","Erlang","Logging"],"sub_categories":["Networking","E-Books","Mesh networks"],"readme":"Overview\n--------\nLager (as in the beer) is a logging framework for Erlang. Its purpose is\nto provide a more traditional way to perform logging in an erlang application\nthat plays nicely with traditional UNIX logging tools like logrotate and\nsyslog.\n\n[![Build Status](https://github.com/erlang-lager/lager/workflows/build/badge.svg)](https://github.com/erlang-lager/lager)\n[![Hex pm](https://img.shields.io/hexpm/v/lager)](https://hex.pm/packages/lager)\n\nFeatures\n--------\n* Finer grained log levels (debug, info, notice, warning, error, critical,\n  alert, emergency)\n* Logger calls are transformed using a parse transform to allow capturing\n  Module/Function/Line/Pid information\n* When no handler is consuming a log level (eg. debug) no event is sent\n  to the log handler\n* Supports multiple backends, including console and file.\n* Supports multiple sinks\n* Rewrites common OTP error messages into more readable messages\n* Support for pretty printing records encountered at compile time\n* Tolerant in the face of large or many log messages, won't out of memory the node\n* Optional feature to bypass log size truncation (\"unsafe\")\n* Supports internal time and date based rotation, as well as external rotation tools\n* Syslog style log level comparison flags\n* Colored terminal output (requires R16+)\n* Map support (requires 17+)\n* Optional load shedding by setting a high water mark to kill (and reinstall)\n  a sink after a configurable cool down timer\n\nContributing\n------------\nWe welcome contributions from the community. We are always excited to get ideas\nfor improving lager.\n\nIf you are looking for an idea to help out, please take a look at our open\nissues - a number of them are tagged with [Help Wanted](https://github.com/erlang-lager/lager/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22)\nand [Easy](https://github.com/erlang-lager/lager/issues?q=is%3Aopen+is%3Aissue+label%3AEasy) - some\nof them are tagged as both! We are happy to mentor people get started with any\nof these issues, and they don't need prior discussion.\n\nThat being said, before you send large changes please open an issue first to\ndiscuss the change you'd like to make along with an idea of your proposal to\nimplement that change.\n\n### PR guidelines ###\n\n* Large changes without prior discussion are likely to be rejected.\n* Changes without test cases are likely to be rejected.\n* Please use the style of the existing codebase when submitting PRs.\n\nWe review PRs and issues at least once a month as described below.\n\nOTP Support Policy\n------------------\nThe lager maintainers intend to support the past three OTP releases from\ncurrent on the main 3.x branch of the project. As of August 2019 that includes\n22, 21 20\n\nLager may or may not run on older OTP releases but it will only be guaranteed\ntested on the previous three OTP releases. If you need a version of lager\nwhich runs on older OTP releases, we recommend you use either the 3.4.0 release\nor the 2.x branch.\n\nMonthly triage cadence\n----------------------\nWe have (at least) monthly issue and PR triage for lager in the #lager room on the\n[freenode](https://freenode.net) IRC network every third Thursday at 2 pm US/Pacific,\n10 pm UTC. You are welcome to join us there to ask questions about lager or\nparticipate in the triage.\n\nUsage\n-----\nTo use lager in your application, you need to define it as a rebar dep or have\nsome other way of including it in Erlang's path. You can then add the\nfollowing option to the erlang compiler flags:\n\n```erlang\n{parse_transform, lager_transform}\n```\n\nAlternately, you can add it to the module you wish to compile with logging\nenabled:\n\n```erlang\n-compile([{parse_transform, lager_transform}]).\n```\n\nBefore logging any messages, you'll need to start the lager application. The\nlager module's `start` function takes care of loading and starting any dependencies\nlager requires.\n\n```erlang\nlager:start().\n```\n\nYou can also start lager on startup with a switch to `erl`:\n\n```erlang\nerl -pa path/to/lager/ebin -s lager\n```\n\nOnce you have built your code with lager and started the lager application,\nyou can then generate log messages by doing the following:\n\n```erlang\nlager:error(\"Some message\")\n```\n\n  Or:\n\n```erlang\nlager:warning(\"Some message with a term: ~p\", [Term])\n```\n\nThe general form is `lager:Severity()` where `Severity` is one of the log levels\nmentioned above.\n\nConfiguration\n-------------\nTo configure lager's backends, you use an application variable (probably in\nyour app.config):\n\n```erlang\n{lager, [\n  {log_root, \"/var/log/hello\"},\n  {handlers, [\n    {lager_console_backend, [{level, info}]},\n    {lager_file_backend, [{file, \"error.log\"}, {level, error}]},\n    {lager_file_backend, [{file, \"console.log\"}, {level, info}]}\n  ]}\n]}.\n```\n\n```log_root``` variable is optional, by default file paths are relative to CWD.\n\nThe available configuration options for each backend are listed in their\nmodule's documentation.\n\nSinks\n-----\nLager has traditionally supported a single sink (implemented as a\n`gen_event` manager) named `lager_event` to which all backends were\nconnected.\n\nLager now supports extra sinks; each sink can have different\nsync/async message thresholds and different backends.\n\n### Sink configuration\n\nTo use multiple sinks (beyond the built-in sink of lager and lager_event), you\nneed to:\n\n1. Setup rebar.config\n2. Configure the backends in app.config\n\n#### Names\n\nEach sink has two names: one atom to be used like a module name for\nsending messages, and that atom with `_lager_event` appended for backend\nconfiguration.\n\nThis reflects the legacy behavior: `lager:info` (or `critical`, or\n`debug`, etc) is a way of sending a message to a sink named\n`lager_event`. Now developers can invoke `audit:info` or\n`myCompanyName:debug` so long as the corresponding `audit_lager_event` or\n`myCompanyName_lager_event` sinks are configured.\n\n#### rebar.config\n\nIn `rebar.config` for the project that requires lager, include a list\nof sink names (without the `_lager_event` suffix) in `erl_opts`:\n\n`{lager_extra_sinks, [audit]}`\n\n#### Runtime requirements\n\nTo be useful, sinks must be configured at runtime with backends.\n\nIn `app.config` for the project that requires lager, for example,\nextend the lager configuration to include an `extra_sinks` tuple with\nbackends (aka \"handlers\") and optionally `async_threshold` and\n`async_threshold_window` values (see **Overload Protection**\nbelow). If async values are not configured, no overload protection\nwill be applied on that sink.\n\n```erlang\n[{lager, [\n          {log_root, \"/tmp\"},\n\n          %% Default handlers for lager/lager_event\n          {handlers, [\n                      {lager_console_backend, [{level, info}]},\n                      {lager_file_backend, [{file, \"error.log\"}, {level, error}]},\n                      {lager_file_backend, [{file, \"console.log\"}, {level, info}]}\n                     ]},\n\n          %% Any other sinks\n          {extra_sinks,\n           [\n            {audit_lager_event,\n             [{handlers,\n               [{lager_file_backend,\n                 [{file, \"sink1.log\"},\n                  {level, info}\n                 ]\n                }]\n              },\n              {async_threshold, 500},\n              {async_threshold_window, 50}]\n            }]\n          }\n         ]\n }\n].\n```\n\nCustom Formatting\n-----------------\nAll loggers have a default formatting that can be overridden.  A formatter is any module that\nexports `format(#lager_log_message{},Config#any())`.  It is specified as part of the configuration\nfor the backend:\n\n```erlang\n{lager, [\n  {handlers, [\n    {lager_console_backend, [{level, info}, {formatter, lager_default_formatter},\n      {formatter_config, [time,\" [\",severity,\"] \", message, \"\\n\"]}]},\n    {lager_file_backend, [{file, \"error.log\"}, {level, error}, {formatter, lager_default_formatter},\n      {formatter_config, [date, \" \", time,\" [\",severity,\"] \",pid, \" \", message, \"\\n\"]}]},\n    {lager_file_backend, [{file, \"console.log\"}, {level, info}]}\n  ]}\n]}.\n```\n\nIncluded is `lager_default_formatter`.  This provides a generic, default\nformatting for log messages using a structure similar to Erlang's\n[iolist](http://learnyousomeerlang.com/buckets-of-sockets#io-lists) which we\ncall \"semi-iolist\":\n\n* Any traditional iolist elements in the configuration are printed verbatim.\n* Atoms in the configuration are treated as placeholders for lager metadata and\n  extracted from the log message.\n    * The placeholders `date`, `time`, `message`, `sev` and `severity` will always exist.\n    * `sev` is an abbreviated severity which is interpreted as a capitalized\n      single letter encoding of the severity level (e.g. `'debug'` -\u003e `$D`)\n    * The placeholders `pid`, `file`, `line`, `module`, `function`, and `node`\n      will always exist if the parse transform is used.\n    * The placeholder `application` may exist if the parse transform is used.\n      It is dependent on finding the applications `app.src` file.\n    * If the error logger integration is used, the placeholder `pid`\n      will always exist and the placeholder `name` may exist.\n    * Applications can define their own metadata placeholder.\n    * A tuple of `{atom(), semi-iolist()}` allows for a fallback for\n      the atom placeholder. If the value represented by the atom\n      cannot be found, the semi-iolist will be interpreted instead.\n    * A tuple of `{atom(), semi-iolist(), semi-iolist()}` represents a\n      conditional operator: if a value for the atom placeholder can be\n      found, the first semi-iolist will be output; otherwise, the\n      second will be used.\n    * A tuple of `{pterm, atom()}` will attempt to lookup\n      the value of the specified atom from the\n      [persistent_term](http://erlang.org/doc/man/persistent_term.html)\n      feature added in OTP 21.2. The default value is `\"\"`. The\n      default value will be used if the key cannot be found or\n      if this formatting term is specified on an OTP release before\n      OTP 21.\n    * A tuple of `{pterm, atom(), semi-iolist()}` will attempt to\n      lookup the value of the specified atom from the persistent_term\n      feature added in OTP 21.2. The default value is the specified\n      semi-iolist(). The default value will be used if the key cannot\n      be found or the if this formatting term is specified on an OTP\n      release before OTP 21.\n\nExamples:\n\n```\n[\"Foo\"] -\u003e \"Foo\", regardless of message content.\n[message] -\u003e The content of the logged message, alone.\n[{pid,\"Unknown Pid\"}] -\u003e \"\u003c?.?.?\u003e\" if pid is in the metadata, \"Unknown Pid\" if not.\n[{pid, [\"My pid is \", pid], [\"Unknown Pid\"]}] -\u003e if pid is in the metadata print \"My pid is \u003c?.?.?\u003e\", otherwise print \"Unknown Pid\"\n[{server,{pid, [\"(\", pid, \")\"], [\"(Unknown Server)\"]}}] -\u003e user provided server metadata, otherwise \"(\u003c?.?.?\u003e)\", otherwise \"(Unknown Server)\"\n[{pterm, pterm_key, \u003c\u003c\"undefined\"\u003e\u003e}] -\u003e if a value for 'pterm_key' is found in OTP 21 (or later) persistent_term storage it is used, otherwise \"undefined\"\n```\n\nUniversal time\n--------------\nBy default, lager formats timestamps as local time for whatever computer\ngenerated the log message.\n\nTo make lager use UTC timestamps, you can set the `sasl` application's\n`utc_log` configuration parameter to `true` in your application configuration\nfile.\n\nExample:\n\n```\n%% format log timestamps as UTC\n[{sasl, [{utc_log, true}]}].\n```\n\nError logger integration\n------------------------\nLager is also supplied with a `error_logger` handler module that translates\ntraditional erlang error messages into a friendlier format and sends them into\nlager itself to be treated like a regular lager log call. To disable this, set\nthe lager application variable `error_logger_redirect` to `false`.\nYou can also disable reformatting for OTP and Cowboy messages by setting variable\n`error_logger_format_raw` to `true`.\n\nIf you installed your own handler(s) into `error_logger`, you can tell\nlager to leave it alone by using the `error_logger_whitelist` environment\nvariable with a list of handlers to allow.\n\n```\n{error_logger_whitelist, [my_handler]}\n```\n\nThe `error_logger` handler will also log more complete error messages (protected\nwith use of `trunc_io`) to a \"crash log\" which can be referred to for further\ninformation. The location of the crash log can be specified by the `crash_log`\napplication variable. If set to `false` it is not written at all.\n\nMessages in the crash log are subject to a maximum message size which can be\nspecified via the `crash_log_msg_size` application variable.\n\nMessages from `error_logger` will be redirected to `error_logger_lager_event` sink\nif it is defined so it can be redirected to another log file.\n\nFor example:\n\n```\n[{lager, [\n         {extra_sinks,\n          [\n           {error_logger_lager_event, \n            [{handlers, [\n              {lager_file_backend, [{file, \"error_logger.log\"}, {level, info}]}]\n              }]\n           }]\n           }]\n}].\n```\nwill send all `error_logger` messages to `error_logger.log` file.\n\nOverload Protection\n-------------------\n\n### Asynchronous mode\n\nPrior to lager 2.0, the `gen_event` at the core of lager operated purely in\nsynchronous mode. Asynchronous mode is faster, but has no protection against\nmessage queue overload. As of lager 2.0, the `gen_event` takes a hybrid\napproach. it polls its own mailbox size and toggles the messaging between\nsynchronous and asynchronous depending on mailbox size.\n\n```erlang\n{async_threshold, 20},\n{async_threshold_window, 5}\n```\n\nThis will use async messaging until the mailbox exceeds 20 messages, at which\npoint synchronous messaging will be used, and switch back to asynchronous, when\nsize reduces to `20 - 5 = 15`.\n\nIf you wish to disable this behaviour, simply set `async_threshold` to `undefined`. It defaults\nto a low number to prevent the mailbox growing rapidly beyond the limit and causing\nproblems. In general, lager should process messages as fast as they come in, so getting\n20 behind should be relatively exceptional anyway.\n\nIf you want to limit the number of messages per second allowed from `error_logger`,\nwhich is a good idea if you want to weather a flood of messages when lots of\nrelated processes crash, you can set a limit:\n\n```erlang\n{error_logger_hwm, 50}\n```\n\nIt is probably best to keep this number small.\n\n### Event queue flushing\n\nWhen the high-water mark is exceeded, lager can be configured to flush all\nevent notifications in the message queue. This can have unintended consequences\nfor other handlers in the same event manager (in e.g. the `error_logger`), as\nevents they rely on may be wrongly discarded. By default, this behavior is enabled,\nbut can be controlled, for the `error_logger` via:\n\n```erlang\n{error_logger_flush_queue, true | false}\n```\n\nor for a specific sink, using the option:\n\n```erlang\n{flush_queue, true | false}\n```\n\nIf `flush_queue` is true, a message queue length threshold can be set, at which\nmessages will start being discarded. The default threshold is `0`, meaning that\nif `flush_queue` is true, messages will be discarded if the high-water mark is\nexceeded, regardless of the length of the message queue. The option to control\nthe threshold is, for `error_logger`:\n\n```erlang\n{error_logger_flush_threshold, 1000}\n```\n\nand for sinks:\n\n```erlang\n{flush_threshold, 1000}\n```\n\n### Sink Killer\n\nIn some high volume situations, it may be preferable to drop all pending log\nmessages instead of letting them drain over time.\n\nIf you prefer, you may choose to use the sink killer to shed load. In this\noperational mode, if the `gen_event` mailbox exceeds a configurable\nhigh water mark, the sink will be killed and reinstalled after a\nconfigurable cool down time.\n\nYou can configure this behavior by using these configuration directives:\n\n```erlang\n{killer_hwm, 1000},\n{killer_reinstall_after, 5000}\n```\n\nThis means if the sink's mailbox size exceeds 1000 messages, kill the\nentire sink and reload it after 5000 milliseconds. This behavior can\nalso be installed into alternative sinks if desired.\n\nBy default, the manager killer *is not installed* into any sink. If\nthe `killer_reinstall_after` cool down time is not specified it defaults\nto 5000.\n\n\"Unsafe\"\n--------\nThe unsafe code pathway bypasses the normal lager formatting code and uses the\nsame code as error_logger in OTP. This provides a marginal speedup to your logging\ncode (we measured between 0.5-1.3% improvement during our benchmarking; others have\nreported better improvements.)\n\nThis is a **dangerous** feature. It *will not* protect you against\nlarge log messages - large messages can kill your application and even your\nErlang VM dead due to memory exhaustion as large terms are copied over and\nover in a failure cascade.  We strongly recommend that this code pathway\nonly be used by log messages with a well bounded upper size of around 500 bytes.\n\nIf there's any possibility the log messages could exceed that limit, you should\nuse the normal lager message formatting code which will provide the appropriate\nsize limitations and protection against memory exhaustion.\n\nIf you want to format an unsafe log message, you may use the severity level (as\nusual) followed by `_unsafe`. Here's an example:\n\n```erlang\nlager:info_unsafe(\"The quick brown ~s jumped over the lazy ~s\", [\"fox\", \"dog\"]).\n```\n\nRuntime loglevel changes\n------------------------\nYou can change the log level of any lager backend at runtime by doing the\nfollowing:\n\n```erlang\nlager:set_loglevel(lager_console_backend, debug).\n```\n\n  Or, for the backend with multiple handles (files, mainly):\n\n```erlang\nlager:set_loglevel(lager_file_backend, \"console.log\", debug).\n```\n\nLager keeps track of the minimum log level being used by any backend and\nsuppresses generation of messages lower than that level. This means that debug\nlog messages, when no backend is consuming debug messages, are effectively\nfree. A simple benchmark of doing 1 million debug log messages while the\nminimum threshold was above that takes less than half a second.\n\nSyslog style loglevel comparison flags\n--------------------------------------\nIn addition to the regular log level names, you can also do finer grained masking\nof what you want to log:\n\n```\ninfo - info and higher (\u003e= is implicit)\n=debug - only the debug level\n!=info - everything but the info level\n\u003c=notice - notice and below\n\u003cwarning - anything less than warning\n```\n\nThese can be used anywhere a loglevel is supplied, although they need to be either\na quoted atom or a string.\n\nInternal log rotation\n---------------------\nLager can rotate its own logs or have it done via an external process. To\nuse internal rotation, use the `size`, `date` and `count` values in the file\nbackend's config:\n\n```erlang\n[{file, \"error.log\"}, {level, error}, {size, 10485760}, {date, \"$D0\"}, {count, 5}]\n```\n\nThis tells lager to log error and above messages to `error.log` and to\nrotate the file at midnight or when it reaches 10mb, whichever comes first,\nand to keep 5 rotated logs in addition to the current one. Setting the\ncount to 0 does not disable rotation, it instead rotates the file and keeps\nno previous versions around. To disable rotation set the size to 0 and the\ndate to \"\".\n\nThe `$D0` syntax is taken from the syntax newsyslog uses in newsyslog.conf.\nThe relevant extract follows:\n\n```\nDay, week and month time format: The lead-in character\nfor day, week and month specification is a `$'-sign.\nThe particular format of day, week and month\nspecification is: [Dhh], [Ww[Dhh]] and [Mdd[Dhh]],\nrespectively.  Optional time fields default to\nmidnight.  The ranges for day and hour specifications\nare:\n\n  hh      hours, range 0 ... 23\n  w       day of week, range 0 ... 6, 0 = Sunday\n  dd      day of month, range 1 ... 31, or the\n          letter L or l to specify the last day of\n          the month.\n\nSome examples:\n  $D0     rotate every night at midnight\n  $D23    rotate every day at 23:00 hr\n  $W0D23  rotate every week on Sunday at 23:00 hr\n  $W5D16  rotate every week on Friday at 16:00 hr\n  $M1D0   rotate on the first day of every month at\n          midnight (i.e., the start of the day)\n  $M5D6   rotate on every 5th day of the month at\n          6:00 hr\n```\n\nOn top of the day, week and month time format from newsyslog,\nhour specification is added from PR [#420](https://github.com/erlang-lager/lager/pull/420)\n\n```\nFormat of hour specification is : [Hmm]\nThe range for minute specification is:\n\n  mm      minutes, range 0 ... 59\n\nSome examples:\n\n  $H00    rotate every hour at HH:00\n  $D12H30 rotate every day at 12:30\n  $W0D0H0 rotate every week on Sunday at 00:00\n```\n\nTo configure the crash log rotation, the following application variables are\nused:\n* `crash_log_size`\n* `crash_log_date`\n* `crash_log_count`\n* `crash_log_rotator`\n\nSee the `.app.src` file for further details.\n\nCustom Log Rotation\n-------------------\nCustom log rotator could be configured with option for `lager_file_backend`\n```erlang\n{rotator, lager_rotator_default}\n```\n\nThe module should provide the following callbacks as `lager_rotator_behaviour`\n\n```erlang\n%% @doc Create a log file\n-callback(create_logfile(Name::list(), Buffer::{integer(), integer()} | any()) -\u003e\n    {ok, {FD::file:io_device(), Inode::integer(), Size::integer()}} | {error, any()}).\n\n%% @doc Open a log file\n-callback(open_logfile(Name::list(), Buffer::{integer(), integer()} | any()) -\u003e\n    {ok, {FD::file:io_device(), Inode::integer(), Size::integer()}} | {error, any()}).\n\n%% @doc Ensure reference to current target, could be rotated\n-callback(ensure_logfile(Name::list(), FD::file:io_device(), Inode::integer(),\n                         Buffer::{integer(), integer()} | any()) -\u003e\n    {ok, {FD::file:io_device(), Inode::integer(), Size::integer()}} | {error, any()}).\n\n%% @doc Rotate the log file\n-callback(rotate_logfile(Name::list(), Count::integer()) -\u003e\n    ok).\n```\n\nSyslog Support\n--------------\nLager syslog output is provided as a separate application:\n[lager_syslog](https://github.com/erlang-lager/lager_syslog). It is packaged as a\nseparate application so lager itself doesn't have an indirect dependency on a\nport driver. Please see the `lager_syslog` README for configuration information.\n\nOther Backends\n--------------\nThere are lots of them! Some connect log messages to AMQP, various logging\nanalytic services ([bunyan](https://github.com/Vagabond/lager_bunyan_formatter),\n[loggly](https://github.com/kivra/lager_loggly), etc), and more. [Looking on\nhex](https://hex.pm/packages?_utf8=✓\u0026search=lager\u0026sort=recent_downloads) or\nusing \"lager BACKEND\" where \"BACKEND\" is your preferred log solution\non your favorite search engine is a good starting point.\n\nException Pretty Printing\n----------------------\nUp to OTP 20:\n\n```erlang\ntry\n    foo()\ncatch\n    Class:Reason -\u003e\n        lager:error(\n            \"~nStacktrace:~s\",\n            [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})])\nend.\n```\n\nOn OTP 21+:\n\n```erlang\ntry\n    foo()\ncatch\n    Class:Reason:Stacktrace -\u003e\n        lager:error(\n            \"~nStacktrace:~s\",\n            [lager:pr_stacktrace(Stacktrace, {Class, Reason})])\nend.\n```\n\nRecord Pretty Printing\n----------------------\nLager's parse transform will keep track of any record definitions it encounters\nand store them in the module's attributes. You can then, at runtime, print any\nrecord a module compiled with the lager parse transform knows about by using the\n`lager:pr/2` function, which takes the record and the module that knows about the record:\n\n```erlang\nlager:info(\"My state is ~p\", [lager:pr(State, ?MODULE)])\n```\n\nOften, `?MODULE` is sufficient, but you can obviously substitute that for a literal module name.\n`lager:pr` also works from the shell.\n\nColored terminal output\n-----------------------\nIf you have Erlang R16 or higher, you can tell lager's console backend to be colored. Simply\nadd to lager's application environment config:\n\n```erlang\n{colored, true}\n```\n\nIf you don't like the default colors, they are also configurable; see\nthe `.app.src` file for more details.\n\nThe output will be colored from the first occurrence of the atom color\nin the formatting configuration. For example:\n\n```erlang\n{lager_console_backend, [{level, info}, {formatter, lager_default_formatter},\n  {formatter_config, [time, color, \" [\",severity,\"] \", message, \"\\e[0m\\r\\n\"]}]]}\n```\n\nThis will make the entire log message, except time, colored. The\nescape sequence before the line break is needed in order to reset the\ncolor after each log message.\n\nTracing\n-------\nLager supports basic support for redirecting log messages based on log message\nattributes. Lager automatically captures the pid, module, function and line at the\nlog message callsite. However, you can add any additional attributes you wish:\n\n```erlang\nlager:warning([{request, RequestID},{vhost, Vhost}], \"Permission denied to ~s\", [User])\n```\n\nThen, in addition to the default trace attributes, you'll be able to trace\nbased on request or vhost:\n\n```erlang\nlager:trace_file(\"logs/example.com.error\", [{vhost, \"example.com\"}], error)\n```\n\nTo persist metadata for the life of a process, you can use `lager:md/1` to store metadata\nin the process dictionary:\n\n```erlang\nlager:md([{zone, forbidden}])\n```\n\nNote that `lager:md` will *only* accept a list of key/value pairs keyed by atoms.\n\nYou can also omit the final argument, and the loglevel will default to\n`debug`.\n\nTracing to the console is similar:\n\n```erlang\nlager:trace_console([{request, 117}])\n```\n\nIn the above example, the loglevel is omitted, but it can be specified as the\nsecond argument if desired.\n\nYou can also specify multiple expressions in a filter, or use the `*` atom as\na wildcard to match any message that has that attribute, regardless of its\nvalue. You may also use the special value `!` to mean, only select if this\nkey is **not** present.\n\nTracing to an existing logfile is also supported (but see **Multiple\nsink support** below):\n\n```erlang\nlager:trace_file(\"log/error.log\", [{module, mymodule}, {function, myfunction}], warning)\n```\n\nTo view the active log backends and traces, you can use the `lager:status()`\nfunction. To clear all active traces, you can use `lager:clear_all_traces()`.\n\nTo delete a specific trace, store a handle for the trace when you create it,\nthat you later pass to `lager:stop_trace/1`:\n\n```erlang\n{ok, Trace} = lager:trace_file(\"log/error.log\", [{module, mymodule}]),\n...\nlager:stop_trace(Trace)\n```\n\nTracing to a pid is somewhat of a special case, since a pid is not a\ndata-type that serializes well. To trace by pid, use the pid as a string:\n\n```erlang\nlager:trace_console([{pid, \"\u003c0.410.0\u003e\"}])\n```\n\n### Filter expressions\nAs of lager 3.3.1, you can also use a 3 tuple while tracing where the second\nelement is a comparison operator. The currently supported comparison operators\nare:\n\n* `\u003c` - less than\n* `=\u003c` - less than or equal\n* `=` - equal to\n* `!=` - not equal to\n* `\u003e` - greater than\n* `\u003e=` - greater than or equal\n\n```erlang\nlager:trace_console([{request, '\u003e', 117}, {request, '\u003c', 120}])\n```\n\nUsing `=` is equivalent to the 2-tuple form.\n\n### Filter composition\nAs of lager 3.3.1 you may also use the special filter composition keys of\n`all` or `any`. For example the filter example above could be\nexpressed as:\n\n```erlang\nlager:trace_console([{all, [{request, '\u003e', 117}, {request, '\u003c', 120}]}])\n```\n\n`any` has the effect of \"OR style\" logical evaluation between filters; `all`\nmeans \"AND style\" logical evaluation between filters. These compositional filters\nexpect a list of additional filter expressions as their values.\n\n### Null filters\nThe `null` filter has a special meaning.  A filter of `{null, false}` acts as\na black hole; nothing is passed through.  A filter of `{null, true}` means\n*everything* passes through. No other values for the null filter are valid and\nwill be rejected.\n\n### Silencing filters\nA special log level, `silence` can be used together with a filter in order\nto suppress specific log output. This can be useful if a backend has been\nconfigured for a particular log level, but a particular set of log messages\nclutters the log. If these come from a dependency, they might be difficult\nto remove entirely, and it might not be desirable to do so in general.\nIn such situations, a trace filter with log level `silence` can turn them\noff selectively, while letting other messages through as before.\n\n### Multiple sink support\n\nIf using multiple sinks, there are limitations on tracing that you\nshould be aware of.\n\nTraces are specific to a sink, which can be specified via trace\nfilters:\n\n```erlang\nlager:trace_file(\"log/security.log\", [{sink, audit_event}, {function, myfunction}], warning)\n```\n\nIf no sink is thus specified, the default lager sink will be used.\n\nThis has two ramifications:\n\n* Traces cannot intercept messages sent to a different sink.\n* Tracing to a file already opened via `lager:trace_file` will only be\n  successful if the same sink is specified.\n\nThe former can be ameliorated by opening multiple traces; the latter\ncan be fixed by rearchitecting lager's file backend, but this has not\nbeen tackled.\n\n### Traces from configuration\n\nLager supports starting traces from its configuration file. The keyword\nto define them is `traces`, followed by a proplist of tuples that define\na backend handler and zero or more filters in a required list,\nfollowed by an optional message severity level.\n\nAn example looks like this:\n\n```erlang\n{lager, [\n  {handlers, [...]},\n  {traces, [\n    %% handler,                         filter,                message level (defaults to debug if not given)\n    {lager_console_backend,             [{module, foo}],       info },\n    {{lager_file_backend, \"trace.log\"}, [{request, '\u003e', 120}], error},\n    {{lager_file_backend, \"event.log\"}, [{module, bar}]             } %% implied debug level here\n  ]}\n]}.\n```\n\nIn this example, we have three traces. One using the console backend, and two\nusing the file backend. If the message severity level is left out, it defaults\nto `debug` as in the last file backend example.\n\nThe `traces` keyword works on alternative sinks too but the same limitations\nand caveats noted above apply.\n\n**IMPORTANT**: You **must** define a severity level in all lager releases\nup to and including 3.1.0 or previous. The 2-tuple form wasn't added until\n3.2.0.\n\nSetting dynamic metadata at compile-time\n----------------------------------------\nLager supports supplying metadata from external sources by registering a \ncallback function. This metadata is also persistent across processes even if \nthe process dies.\n\nIn general use you won't need to use this feature. However it is useful in \nsituations such as:\n * Tracing information provided by \n   [seq_trace](http://erlang.org/doc/man/seq_trace.html)\n * Contextual information about your application\n * Persistent information which isn't provided by the default placeholders\n * Situations where you would have to set the metadata before every logging call \n\nYou can add the callbacks by using the `{lager_parse_transform_functions, X}` \noption.  It is only available when using `parse_transform`. In rebar, you can \nadd it to `erl_opts` as below:\n\n```erlang\n{erl_opts, [{parse_transform, lager_transform}, \n            {lager_function_transforms, \n              [\n                 %% Placeholder              Resolve type  Callback tuple\n                {metadata_placeholder,       on_emit,      {module_name, function_name}},\n                {other_metadata_placeholder, on_log,       {module_name, function_name}}\n              ]}]}.\n```\n\nThe first atom is the placeholder atom used for the substitution in your custom\n formatter. See [Custom Formatting](#custom-formatting) for more information.\n\nThe second atom is the resolve type. This specify the callback to resolve at \nthe time of the message being emitted or at the time of the logging call. You \nhave to specify either the atom `on_emit` or `on_log`. There is not a 'right' \nresolve type to use, so please read the uses/caveats of each and pick the option \nwhich fits your requirements best.\n \n`on_emit`:\n  * The callback functions are not resolved until the message is emitted by the\n    backend.\n  * If the callback function cannot be resolved, not loaded or produces \n    unhandled errors then `undefined` will be returned.\n  * Since the callback function is dependent on a process, there is the \n    chance that message will be emitted after the dependent process has died \n    resulting in `undefined` being returned. This process can also be your own\n    process\n \n`on_log`:\n  * The callback functions are resolved regardless whether the message is  \n    emitted or not\n  * If the callback function cannot be resolved or not loaded the errors are \n    not handled by lager itself.\n  * Any potential errors in callback should be handled in the callback function\n    itself.\n  * Because the function is resolved at log time there should be less chance\n    of the dependent process dying before you can resolve it, especially if\n    you are logging from the app which contains the callback.\n\nThe third element is the callback to your function consisting of a tuple in the\nform `{Module Function}`. The callback should look like the following \nregardless if using `on_emit` or `on_log`:  \n  * It should be exported\n  * It should takes no arguments e.g. has an arity of 0\n  * It should return any traditional iolist elements or the atom `undefined`\n  * For errors generated within your callback see the resolve type documentation\n    above.\n\nIf the callback returns `undefined` then it will follow the same fallback and\nconditional operator rules as documented in the \n[Custom Formatting](#custom-formatting) section. \n\nThis example would work with `on_emit` but could be unsafe to use with \n`on_log`. If the call failed in `on_emit` it would default to `undefined`, \nhowever with `on_log` it would error.\n\n```erlang\n-export([my_callback/0]).\n\nmy_callback() -\u003e\n  my_app_serv:call('some options').\n```\n\nThis example would be to safe to work with both `on_emit` and `on_log`\n\n```erlang\n-export([my_callback/0]).\n\nmy_callback() -\u003e\n  try my_app_serv:call('some options') of\n    Result -\u003e\n      Result\n  catch\n    _ -\u003e\n      %% You could define any traditional iolist elements you wanted here\n      undefined\n  end.\n```\n\nNote that the callback can be any Module:Function/0. It does not have be part \nof your application. For example you could use `cpu_sup:avg1/0` as your  \ncallback function like so `{cpu_avg1, on_emit, {cpu_sup, avg1}}`\n\n\nExamples:\n\n```erlang\n-export([reductions/0]).\n\nreductions() -\u003e\n  proplists:get_value(reductions, erlang:process_info(self())).\n```\n\n```erlang\n-export([seq_trace/0]).\n\nseq_trace() -\u003e\n  case seq_trace:get_token(label) of\n    {label, TraceLabel} -\u003e\n      TraceLabel;\n    _ -\u003e\n      undefined\n  end.\n```\n\n**IMPORTANT**: Since `on_emit` relies on function calls injected at the\npoint where a log message is emitted, your logging performance (ops/sec)\nwill be impacted by what the functions you call do and how much latency they\nmay introduce. This impact will even greater with `on_log` since the calls\nare injected at the point a message is logged.\n\nSetting the truncation limit at compile-time\n--------------------------------------------\nLager defaults to truncating messages at 4096 bytes, you can alter this by\nusing the `{lager_truncation_size, X}` option. In rebar, you can add it to\n`erl_opts`:\n\n```erlang\n{erl_opts, [{parse_transform, lager_transform}, {lager_truncation_size, 1024}]}.\n```\n\nYou can also pass it to `erlc`, if you prefer:\n\n```\nerlc -pa lager/ebin +'{parse_transform, lager_transform}' +'{lager_truncation_size, 1024}' file.erl\n```\n\nSuppress applications and supervisors start/stop logs\n-----------------------------------------------------\nIf you don't want to see supervisors and applications start/stop logs in debug\nlevel of your application, you can use these configs to turn it off:\n\n```erlang\n{lager, [{suppress_application_start_stop, true},\n         {suppress_supervisor_start_stop, true}]}\n```\n\nSys debug functions\n--------------------\n\nLager provides an integrated way to use sys 'debug functions'. You can install a debug\nfunction in a target process by doing\n\n```erlang\nlager:install_trace(Pid, notice).\n```\n\nYou can also customize the tracing somewhat:\n\n```erlang\nlager:install_trace(Pid, notice, [{count, 100}, {timeout, 5000}, {format_string, \"my trace event ~p ~p\"]}).\n```\n\nThe trace options are currently:\n\n* timeout - how long the trace stays installed: `infinity` (the default) or a millisecond timeout\n* count - how many trace events to log: `infinity` (default) or a positive number\n* format_string - the format string to log the event with. *Must* have 2 format specifiers for the 2 parameters supplied.\n\nThis will, on every 'system event' for an OTP process (usually inbound messages, replies\nand state changes) generate a lager message at the specified log level.\n\nYou can remove the trace when you're done by doing:\n\n```erlang\nlager:remove_trace(Pid).\n```\n\nIf you want to start an OTP process with tracing enabled from the very beginning, you can do something like this:\n\n```erlang\ngen_server:start_link(mymodule, [], [{debug, [{install, {fun lager:trace_func/3, lager:trace_state(undefined, notice, [])}}]}]).\n```\n\nThe third argument to the trace_state function is the Option list documented above.\n\nConsole output to another group leader process\n----------------------------------------------\n\nIf you want to send your console output to another group_leader (typically on\nanother node) you can provide a `{group_leader, Pid}` argument to the console\nbackend. This can be combined with another console config option, `id` and\ngen_event's `{Module, ID}` to allow remote tracing of a node to standard out via\nnodetool:\n\n```erlang\n    GL = erlang:group_leader(),\n    Node = node(GL),\n    lager_app:start_handler(lager_event, {lager_console_backend, Node},\n         [{group_leader, GL}, {level, none}, {id, {lager_console_backend, Node}}]),\n    case lager:trace({lager_console_backend, Node}, Filter, Level) of\n         ...\n```\n\nIn the above example, the code is assumed to be running via a `nodetool rpc`\ninvocation so that the code is executing on the Erlang node, but the\ngroup_leader is that of the reltool node (eg. appname_maint_12345@127.0.0.1).\n\nIf you intend to use tracing with this feature, make sure the second parameter\nto start_handler and the `id` parameter match. Thus when the custom group_leader\nprocess exits, lager will remove any associated traces for that handler.\n\nElixir Support\n--------------\n\nThere are 2 ways in which Lager can be leveraged in an Elixir project:\n\n1. Lager Backend for Elixir Logger\n2. Directly\n\n### Lager Backend for Elixir Logger\n\n[Elixir's Logger](https://hexdocs.pm/logger/Logger.html) is the idiomatic way\nto add logging into elixir code. Logger has a plug-in model,\nallowing for different logging [Backends](https://hexdocs.pm/logger/Logger.html#module-backends)\nto be used without the need to change the logging code within your project.\n\nThis approach will benefit from the fact that most elixir libs and frameworks\nare likely to use the elixir Logger and as such logging will all flow via the\nsame logging mechanism.\n\nIn [elixir 1.5 support for parse transforms was deprecated](https://github.com/elixir-lang/elixir/issues/5762).\nTaking the \"Lager as a Logger Backend\" approach is likely bypass any related \nregression issues that would be introduced into a project which is using lager \ndirectly when updating to elixir 1.5.\n\nThere are open source elixir Logger backends for Lager available:\n- [LagerLogger](https://github.com/PSPDFKit-labs/lager_logger)\n- [LoggerLagerBackend](https://github.com/jonathanperret/logger_lager_backend)\n\n### Directly\n\nIt is fully possible prior to elixir 1.5 to use lager and all its features\ndirectly.\n\nAfter elixir 1.5 there is no support for parse transforms, and it is\nrecommended to use an elixir wrapper for the lager api that provides compile time\nlog level exclusion via elixir macros when opting for direct use of lager.\n\nIncluding Lager as a dependency:\n``` elixir\n# mix.exs\ndef application do\n  [\n    applications: [:lager],\n    erl_opts: [parse_transform: \"lager_transform\"]\n  ]\nend\n\ndefp deps do\n  [{:lager, \"~\u003e 3.2\"}]\nend\n```\n\nExample Configuration:\n``` elixir\n# config.exs\nuse Mix.Config\n\n# Stop lager writing a crash log\nconfig :lager, :crash_log, false\n\nconfig :lager,\n  log_root: '/var/log/hello',\n  handlers: [\n    lager_console_backend: :info,\n    lager_file_backend: [file: \"error.log\", level: :error],\n    lager_file_backend: [file: \"console.log\", level: :info]\n  ]\n```\n\nThere is a known issue where Elixir's Logger and Lager both contest for the\nErlang `error_logger` handle if used side by side.\n\nIf using both add the following to your `config.exs`:\n```elixir\n# config.exs\nuse Mix.Config\n\n# Stop lager redirecting :error_logger messages\nconfig :lager, :error_logger_redirect, false\n\n# Stop lager removing Logger's :error_logger handler\nconfig :lager, :error_logger_whitelist, [Logger.ErrorHandler]\n```\n\nExample Usage:\n``` elixir\n:lager.error('Some message')\n:lager.warning('Some message with a term: ~p', [term])\n```\n\n3.x Changelog\n-------------\n3.9.2 - 14 May 2021\n\n   * Bugfix: Prevent \"a term is constructed but never used\" warnings (#547)\n   * Bugfix: Update CI test matrix to include OTP 24 (#551)\n\n3.9.1 - 2 March 2021\n\n   * Bugfix: More log_root fun (#543)\n   * Bugfix: Use GHA for all the CIs\n\n3.9.0 - 24 February 2021\n\n    * Bugfix: Try to make a log root of \"log\" more sensible (#540)\n    * Feature: Further adapt to OTP 24 (also remove pre OTP 21 code),\n               adopt Github Actions for tests\n\n3.8.2 - 4 February 2021\n\n    * Bugfix: Make directory expansion return an absolute path (#535)\n    * Feature: Write crash.log under the log_root location (#536)\n    * Bugfix: Handle line numbering correctly in forthcoming OTP 24 release (#537)\n\n3.8.1 - 28 August 2020\n\n    * Feature: Allow metadata fields to be whitelisted in log formatting (#514)\n    * Feature: Enable a persistent_term log formatter (#530) (#531)\n    * Bugfix: Handle gen_statem crashes in newer OTP releases correctly (#523)\n    * Cleanup: Add a hex badge (#525)\n    * Cleanup: Fix Travis CI badge link\n    * Policy: Officially ending support for OTP 20 (Support OTP 21, 22, 23)\n\n3.8.0 - 9 August 2019\n\n    * Breaking API change: Modify the `lager_rotator_behaviour` to pass in a\n      file's creation time to `ensure_logfile/5` to be used to determine if\n      file has changed on systems where inodes are not available (i.e.\n      `win32`). The return value from `create_logfile/2`, `open_logfile/2` and\n      `ensure_logfile/5` now requires ctime to be returned (#509)\n    * Bugfix: ensure log file rotation works on `win32` (#509)\n    * Bugfix: ensure test suite passes on `win32` (#509)\n    * Bugfix: ensure file paths with Unicode are formatted properly (#510)\n\n3.7.0 - 24 May 2019\n\n    * Policy: Officially ending support for OTP 19 (Support OTP 20, 21, 22)\n    * Cleanup: Fix all dialyzer errors\n    * Bugfix: Minor changes to FSM/statem exits in OTP 22.\n\n3.6.10 - 30 April 2019\n\n    * Documentation: Fix pr_stacktrace invocation example (#494)\n    * Bugfix: Do not count suppressed messages for message drop counts (#499)\n\n3.6.9 - 13 March 2019\n\n    * Bugfix: Fix file rotation on windows (#493)\n\n3.6.8 - 21 December 2018\n\n    * Documentation: Document the error_logger_whitelist environment variable. (#489)\n    * Bugfix: Remove the built in handler inside of OTP 21 `logger` system. (#488)\n    * Bugfix: Cleanup unneeded check for is_map (#486)\n    * Bugfix: Cleanup ranch errors treated as cowboy errors (#485)\n    * Testing: Remove OTP 18 from TravisCI testing matrix\n\n3.6.7 - 14 October 2018\n\n    * Bugfix: fix tracing to work with OTP21 #480\n\n3.6.6 - 24 September 2018\n\n    * Bugfix: When printing records, handle an improper list correctly. #478\n    * Bugfix: Fix various tests and make some rotation code more explicit. #476\n    * Bugfix: Make sure not to miscount messages during high-water mark check. #475\n\n3.6.5 - 3 September 2018\n\n    * Feature: Allow the console backend to redirect output to a remote node #469\n    * Feature: is_loggble - support for severity as atom #472\n    * Bugfix: Prevent silent dropping of messages when hwm is exceeded #467\n    * Bugfix: rotation - default log file not deleted #474\n    * Bugfix: Handle strange crash report from gen_statem #473\n    * Documentation: Various markup fixes: #468 #470\n\n3.6.4 - 11 July 2018\n\n    * Bugfix: Reinstall handlers after a sink is killed #459\n    * Bugfix: Fix platform_define matching not to break on OSX Mojave #461\n    * Feature: Add support for installing a sys trace function #462\n\n3.6.3 - 6 June 2018\n\n    * OTP 21 support\n\n3.6.2 - 26 April 2018\n\n    * Bugfix: flush_threshold not working (#449)\n    * Feature: Add `node` as a formatting option (#447)\n    * Documentation: Update Elixir section with information about parse_transform (#446)\n    * Bugfix: Correct default console configuration to use \"[{level,info}]\" instead (#445)\n    * Feature: Pretty print lists of records at top level and field values with lager:pr (#442)\n    * Bugfix: Ignore return value of lager:dispatch_log in lager.hrl (#441)\n\n3.6.1 - 1 February 2018\n\n    * Bugfix: Make a few corrections to the recent mailbox flushing changes (#436)\n    * Bugfix: add flush options to proplist validation (#439)\n    * Bugfix: Don't log when we dropped 0 messages (#440)\n\n3.6.0 - 16 January 2018\n\n    * Feature: Support logging with macros per level (#419)\n    * Feature: Support custom file rotation handler; support hourly file\n               rotation (#420)\n    * Feature: Optionally reverse pretty stacktraces (so errors are\n               at the top and the failed function call is at the bottom.)\n               (#424)\n    * Bugfix:  Handle OTP 20 gen_server failure where client pid\n               is dead. (#426)\n    * Feature: Optionally don't flush notify messages at\n               high water mark. (#427)\n    * Bugfix:  Handle another stacktrace format (#429)\n    * Bugfix:  Fix test failure using macros on OTP 18 (#430)\n    * Policy:  Remove all code which supports R15 (#432)\n\n3.5.2 - 19 October 2017\n\n    * Bugfix: Properly check for unicode characters in potentially deep\n              character list. (#417)\n\n3.5.1 - 15 June 2017\n\n    * Doc fix: Missed a curly brace in an example. (#412)\n    * Feature: Dynamic metadata functions (#392) - It is now possible to\n               dynamically add metadata to lager messages. See the \"dynamic\n               metadata\" section above for more information.\n    * Doc fix: Add information about the \"application\" placeholder. (#414)\n\n3.5.0 - 28 May 2017\n\n    * Bugfix: Support OTP 20 gen_event messages (#410)\n    * Feature: Enable console output to standard_error.\n               Convert to proplist configuration style (like file handler)\n               Deprecate previous configuration directives (#409)\n    * Bugfix: Enable the event shaper to filter messages before they're\n              counted; do not count application/supervisor start/stops\n              toward high water mark. (#411)\n    * Docs: Add PR guidelines; add info about the #lager chat room on freenode.\n\n3.4.2 - 26 April 2017\n\n    * Docs: Document how to make lager use UTC timestamps (#405)\n    * Docs: Add a note about our triage cadence.\n    * Docs: Update lager_syslog URL\n    * Docs: Document placeholders for error_logger integration (#404)\n    * Feature: Add hex.pm metadata and full rebar3 support.\n\n3.4.1 - 28 March 2017\n\n    * Docs: Added documentation around using lager in the context of elixir applications (#398)\n    * Bugfix: Properly expand paths when log_root is set. (#386)\n    * Policy: Removed R15 from Travis configuration\n\n3.4.0 - 16 March 2017\n\n    * Policy: Adopt official OTP support policy. (This is the **last** lager 3.x release\n      that will support R15.)\n    * Test: Fix timeouts, R15 missing functions on possibly long-running tests in Travis. (#394, #395)\n    * Feature: capture and log metadata from error_logger messages (#397)\n    * Feature: Expose new trace filters and enable filter composition (#389)\n    * Feature: Log crashes from gen_fsm and gen_statem correctly (#391)\n    * Docs: Typo in badge URL (#390)\n\n3.3.0 - 16 February 2017\n\n    * Docs: Fix documentation to make 'it' unambiguous when discussing asynchronous\n      operation. (#387)\n    * Test: Fix test flappiness due to insufficient sanitation between test runs (#384, #385)\n    * Feature: Allow metadata only logging. (#380)\n    * Feature: Add an upper case severity formatter (#372)\n    * Feature: Add support for suppressing start/stop messages from supervisors (#368)\n    * Bugfix: Fix ranch crash messages (#366)\n    * Test: Update Travis config for 18.3 and 19.0 (#365)\n\n3.2.4 - 11 October 2016\n\n    * Test: Fix dialyzer warnings.\n\n3.2.3 - 29 September 2016\n\n    * Dependency: Update to goldrush 0.19\n\n3.2.2 - 22 September 2016\n\n    * Bugfix: Backwards-compatibility fix for `{crash_log, undefined}` (#371)\n    * Fix documentation/README to reflect the preference for using `false`\n      as the `crash_log` setting value rather than `undefined` to indicate\n      that the crash log should not be written (#364)\n    * Bugfix: Backwards-compatibility fix for `lager_file_backend` \"legacy\"\n      configuration format (#374)\n\n3.2.1 - 10 June 2016\n\n    * Bugfix: Recent `get_env` changes resulted in launch failure (#355)\n    * OTP: Support typed records for Erlang 19.0 (#361)\n\n3.2.0 - 08 April 2016\n\n    * Feature: Optional sink killer to shed load when mailbox size exceeds a\n      configurable high water mark (#346)\n    * Feature: Export `configure_sink/2` so users may dynamically configure\n      previously setup and parse transformed sinks from their own code. (#342)\n    * Feature: Re-enable Travis CI and update .travis.yml (#340)\n    * Bugfix: Fix test race conditions for Travis CI (#344)\n    * Bugfix: Add the atom 'none' to the log_level() type so downstream\n      users won't get dialyzer failures if they use the 'none' log level. (#343)\n    * Bugfix: Fix typo in documentation. (#341)\n    * Bugfix: Fix OTP 18 test failures due to `warning_map/0` response\n      change. (#337)\n    * Bugfix: Make sure traces that use the file backend work correctly\n      when specified in lager configuration. (#336)\n    * Bugfix: Use `lager_app:get_env/3` for R15 compatibility. (#335)\n    * Bugfix: Make sure lager uses `id` instead of `name` when reporting\n      supervisor children failures. (The atom changed in OTP in 2014.) (#334)\n    * Bugfix: Make lager handle improper iolists (#327)\n\n3.1.0 - 27 January 2016\n\n    * Feature: API calls to a rotate handler, sink or all.  This change\n      introduces a new `rotate` message for 3rd party lager backends; that's\n      why this is released as a new minor version number. (#311)\n\n3.0.3 - 27 January 2016\n\n    * Feature: Pretty printer for human readable stack traces (#298)\n    * Feature: Make error reformatting optional (#305)\n    * Feature: Optional and explicit sink for error_logger messages (#303)\n    * Bugfix: Always explicitly close a file after its been rotated (#316)\n    * Bugfix: If a relative path already contains the log root, do not add it again (#317)\n    * Bugfix: Configure and start extra sinks before traces are evaluated (#307)\n    * Bugfix: Stop and remove traces correctly (#306)\n    * Bugfix: A byte value of 255 is valid for Unicode (#300)\n    * Dependency: Bump to goldrush 0.1.8 (#313)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferlang-lager%2Flager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferlang-lager%2Flager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferlang-lager%2Flager/lists"}