Projects
Kolab:Winterfell
erlang-lager
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 3
View file
erlang-lager.spec
Changed
@@ -7,7 +7,7 @@ %global debug_package %{nil} Name: erlang-%{realname} -Version: 2.1.0 +Version: 3.1.0 Release: 1%{?dist} Summary: A logging framework for Erlang/OTP Group: Development/Languages @@ -18,9 +18,6 @@ %endif Source0: https://github.com/%{upstream}/%{realname}/archive/%{version}/%{realname}-%{version}.tar.gz -Patch1: lager-2.1.0-tmpfs.patch -Patch2: lager-2.1.0-test-noproc.patch - BuildRequires: erlang-rebar BuildRequires: erlang-goldrush >= 0.1.6 Requires: erlang-compiler%{?_isa} @@ -44,8 +41,6 @@ %prep %setup -q -n %{realname}-%{version} -%patch1 -p1 -%patch2 -p1 %build rebar compile -v
View file
lager-2.1.0-test-noproc.patch
Deleted
@@ -1,12 +0,0 @@ -Only in lager-2.1.0/: .eunit -diff -ur lager-2.1.0.1.tmpfs/test/lager_test_backend.erl lager-2.1.0/test/lager_test_backend.erl ---- lager-2.1.0.1.tmpfs/test/lager_test_backend.erl 2014-11-03 17:57:48.000000000 +0100 -+++ lager-2.1.0/test/lager_test_backend.erl 2015-05-17 14:53:04.953873200 +0200 -@@ -651,7 +651,6 @@ - TestBody("bad arg1",badarg1,"gen_server crash terminated with reason: bad argument in crash:handle_call/3"), - TestBody("bad arg2",badarg2,"gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([\"foo\",bar]) in crash:handle_call/3"), - TestBody("bad record",badrecord,"gen_server crash terminated with reason: bad record state in crash:handle_call/3"), -- TestBody("noproc",noproc,"gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)"), - TestBody("badfun",badfun,"gen_server crash terminated with reason: bad function booger in crash:handle_call/3") - ] - }.
View file
lager-2.1.0-tmpfs.patch
Deleted
@@ -1,35 +0,0 @@ -Only in lager-2.1.0: deps -Only in lager-2.1.0: ebin -diff -ur lager-2.1.0.orig/rebar.config lager-2.1.0/rebar.config ---- lager-2.1.0.orig/rebar.config 2014-11-03 17:57:48.000000000 +0100 -+++ lager-2.1.0/rebar.config 2015-05-15 16:58:51.418877768 +0200 -@@ -1,7 +1,7 @@ - {erl_opts, [debug_info, warn_untyped_record]}. - {erl_first_files, ["src/lager_util.erl"]}. - {deps, [ -- {goldrush, "0\.1\.6", -+ {goldrush, "0.1.6", - {git, "git://github.com/DeadZen/goldrush.git", {tag, "0.1.6"}}} - ]}. - -diff -ur lager-2.1.0.orig/src/lager_file_backend.erl lager-2.1.0/src/lager_file_backend.erl ---- lager-2.1.0.orig/src/lager_file_backend.erl 2014-11-03 17:57:48.000000000 +0100 -+++ lager-2.1.0/src/lager_file_backend.erl 2015-05-17 14:13:12.005066053 +0200 -@@ -706,17 +706,6 @@ - {ok, Bin3} = file:read_file("foo.log"), - ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}])) - end -- }, -- {"tracing with options should work", -- fun() -> -- file:delete("foo.log"), -- {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}], [{size, 20}, {check_interval, 1}]), -- lager:error("Test message"), -- ?assertNot(filelib:is_regular("foo.log.0")), -- lager:error("Test message"), -- timer:sleep(10), -- ?assert(filelib:is_regular("foo.log.0")) -- end - } - ] - }.
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +erlang-lager (3.1.0-0~kolab1) unstable; urgency=medium + + * Bump version to 3.1.0. + + -- Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Wed, 16 Mar 2016 21:39:31 +0100 + erlang-lager (2.1.0-1~kolab1) unstable; urgency=medium * Bump version to 2.1.0.
View file
debian.series
Changed
@@ -1,2 +0,0 @@ -lager-2.1.0-tmpfs.patch -lager-2.1.0-test-noproc.patch
View file
erlang-lager.dsc
Changed
@@ -2,7 +2,7 @@ Source: erlang-lager Binary: erlang-lager Architecture: any -Version: 2.1.0-1~kolab1 +Version: 3.1.0-0~kolab1 Maintainer: Philipp Huebner <debalance@debian.org> Uploaders: Christoph Erhardt <kolab@sicherha.de> Homepage: https://github.com/basho/lager @@ -11,5 +11,5 @@ Package-List: erlang-lager deb libs optional Files: - 00000000000000000000000000000000 0 lager_2.1.0.tar.gz + 00000000000000000000000000000000 0 lager-3.1.0.tar.gz 00000000000000000000000000000000 0 debian.tar.gz
View file
lager-2.1.0.tar.gz/README.md -> lager-3.1.0.tar.gz/README.md
Changed
@@ -13,12 +13,14 @@ 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 +* 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+) @@ -27,8 +29,8 @@ 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 +some other way of including it in Erlang's path. You can then add the +following option to the erlang compiler flags: ```erlang {parse_transform, lager_transform} @@ -42,7 +44,7 @@ ``` 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 module's `start` function takes care of loading and starting any dependencies lager requires. ```erlang @@ -68,7 +70,7 @@ lager:warning("Some message with a term: ~p", [Term]) ``` -The general form is lager:Severity() where Severity is one of the log levels +The general form is `lager:Severity()` where `Severity` is one of the log levels mentioned above. Configuration @@ -78,6 +80,7 @@ ```erlang {lager, [ + {log_root, "/var/log/hello"}, {handlers, [ {lager_console_backend, info}, {lager_file_backend, [{file, "error.log"}, {level, error}]}, @@ -86,13 +89,93 @@ ]}. ``` +```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. + +```erlang +[{lager, [ + {log_root, "/tmp"}, + + %% Default handlers for lager/lager_event + {handlers, [ + {lager_console_backend, 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 overriden. A formatter is any module that -exports format(#lager_log_message{},Config#any()). It is specified as part of the configuration +exports `format(#lager_log_message{},Config#any())`. It is specified as part of the configuration for the backend: ```erlang @@ -106,49 +189,73 @@ ]}. ``` -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. +Included is `lager_default_formatter`. This provides a generic, default formatting for log messages using a structure similar to Erlang's [iolist](http://learnyousomeerlang.com/buckets-of-sockets#io-lists) which we call "semi-iolist": -``` -["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" -``` +* 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. + * 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. -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. +Examples: ``` +["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. -[{server,[$(,{pid,"Unknown Server"},$)]}}] -> user provided server metadata, otherwise "(<?.?.?>)", otherwise "(Unknown Server)" +[{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)" ``` Error logger integration ------------------------ -Lager is also supplied with a error_logger handler module that translates +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`. +You can also disable reformatting for OTP and Cowboy messages by setting variable +`error_logger_format_raw` to `true`. -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 +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. +specified via the `crash_log_msg_size` application variable. + +Messages from `error_logger` will be redirected to `error_logger_lager_event` sink +if it is defined so it can be redirected to another log file. +For example: +``` +[{lager, [ + {extra_sinks, + [ + {error_logger_lager_event, + [{handlers, [ + {lager_file_backend, [{file, "error_logger.log"}, {level, info}]}] + }] + }] + }] +}]. +``` +Will send all `error_logger` messages to `error_logger.log` file. Overload Protection ------------------- -Prior to lager 2.0, the gen_event at the core of lager operated purely in +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 +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. @@ -161,12 +268,12 @@ 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 +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, +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: @@ -176,6 +283,30 @@ It is probably best to keep this number small. +"Unsafe" +-------- +The unsafe code pathway bypasses the normal lager formatting code and uses the +same code as error_logger in OTP. This provides a marginal speedup to your logging +code (we measured between 0.5-1.3% improvement during our benchmarking; others have +reported better improvements.) + +This is a **dangerous** feature. It *will not* protect you against +large log messages - large messages can kill your application and even your +Erlang VM dead due to memory exhaustion as large terms are copied over and +over in a failure cascade. We strongly recommend that this code pathway +only be used by log messages with a well bounded upper size of around 500 bytes. + +If there's any possibility the log messages could exceed that limit, you should +use the normal lager message formatting code which will provide the appropriate +size limitations and protection against memory exhaustion. + +If you want to format an unsafe log message, you may use the severity level (as +usual) followed by `_unsafe`. Here's an example: + +```erlang +lager:info_unsafe("The quick brown ~s jumped over the lazy ~s", ["fox", "dog"]). +``` + Runtime loglevel changes ------------------------ You can change the log level of any lager backend at runtime by doing the @@ -191,8 +322,8 @@ 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 +Lager keeps track of the minimum log level being used by any backend and +suppresses 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. @@ -216,21 +347,21 @@ 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 +use internal rotation, use the `size`, `date` and `count` values in the file backend's config: ```erlang -[{name, "error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}] +[{file, "error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 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 +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 `$D0` syntax is taken from the syntax newsyslog uses in newsyslog.conf. The relevant extract follows: ``` @@ -261,18 +392,18 @@ To configure the crash log rotation, the following application variables are used: -* crash_log_size -* crash_log_date -* crash_log_count +* `crash_log_size` +* `crash_log_date` +* `crash_log_count` -See the .app.src file for further details. +See the `.app.src` file for further details. Syslog Support -------------- -Lager syslog output is provided as a separate application; +Lager syslog output is provided as a separate application: [lager_syslog](https://github.com/basho/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. +separate application so lager itself doesn't have an indirect dependency on a +port driver. Please see the `lager_syslog` README for configuration information. Older Backends -------------- @@ -280,31 +411,56 @@ lager available, but they may not have been updated to the new API. As they are updated, links to them can be re-added here. +Exception Pretty Printing +---------------------- + +```erlang +try + foo() +catch + Class:Reason -> + lager:error( + "~nStacktrace:~s", + [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})]) +end. +``` + Record Pretty Printing ---------------------- Lager's parse transform will keep track of any record definitions it encounters and store them in the module's attributes. You can then, at runtime, print any record a module compiled with the lager parse transform knows about by using the -lager:pr/2 function, which takes the record and the module that knows about the record: +`lager:pr/2` function, which takes the record and the module that knows about the record: ```erlang lager:info("My state is ~p", [lager:pr(State, ?MODULE)]) ``` -Often, ?MODULE is sufficent, but you can obviously substitute that for a literal module name. -lager:pr also works from the shell. +Often, `?MODULE` is sufficent, but you can obviously substitute that for a literal module name. +`lager:pr` also works from the shell. Colored terminal output ----------------------- -If you have erlang R16 or higher, you can tell lager's console backend to be colored. Simply -add +If you have Erlang R16 or higher, you can tell lager's console backend to be colored. Simply +add to lager's application environment config: ```erlang {colored, true} ``` -To lager's application environment config. If you don't like the default colors, they are -also configurable, see the app.src file for more details. +If you don't like the default colors, they are also configurable; see +the `.app.src` file for more details. + +The output will be colored from the first occurrence of the atom color +in the formatting configuration. For example: + +```erlang +{lager_console_backend, [info, {lager_default_formatter, [time, color, " [",severity,"] ", message, "\e[0m\r\n"]}]} +``` + +This will make the entire log message, except time, colored. The +escape sequence before the line break is needed in order to reset the +color after each log message. Tracing ------- @@ -323,17 +479,17 @@ lager:trace_file("logs/example.com.error", [{vhost, "example.com"}], error) ``` -To persist metadata for the life of a process, you can use lager:md/1 to store metadata +To persist metadata for the life of a process, you can use `lager:md/1` to store metadata in the process dictionary: ```erlang lager:md([{zone, forbidden}]) ``` -Note that lager:md will *only* accept a list of key/value pairs keyed by atoms. +Note that `lager:md` will *only* accept a list of key/value pairs keyed by atoms. You can also omit the final argument, and the loglevel will default to -'debug'. +`debug`. Tracing to the console is similar: @@ -344,22 +500,22 @@ 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 +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 value. -Tracing to an existing logfile is also supported, if you wanted to log -warnings from a particular module to the default error.log: +Tracing to an existing logfile is also supported (but see **Multiple +sink support** below): ```erlang -lager:trace_file("log/error.log", [{module, mymodule}], warning) +lager:trace_file("log/error.log", [{module, mymodule}, {function, myfunction}], 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 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: +that you later pass to `lager:stop_trace/1`: ```erlang {ok, Trace} = lager:trace_file("log/error.log", [{module, mymodule}]), @@ -378,28 +534,72 @@ element is a comparison operator. The currently supported comparison operators are: -* '<' - less than -* '=' - equal to -* '>' - greater than +* `<` - less than +* `=` - equal to +* `>` - greater than ```erlang lager:trace_console([{request, '>', 117}, {request, '<', 120}]) ``` -Using '=' is equivalent to the 2-tuple form. +Using `=` is equivalent to the 2-tuple form. + +### Multiple sink support + +If using multiple sinks, there are limitations on tracing that you +should be aware of. + +Traces are specific to a sink, which can be specified via trace +filters: + +```erlang +lager:trace_file("log/security.log", [{sink, audit}, {function, myfunction}], warning) +``` + +If no sink is thus specified, the default lager sink will be used. + +This has two ramifications: + +* Traces cannot intercept messages sent to a different sink. +* Tracing to a file already opened via `lager:trace_file` will only be + successful if the same sink is specified. + +The former can be ameliorated by opening multiple traces; the latter +can be fixed by rearchitecting lager's file backend, but this has not +been tackled. 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: +using the `{lager_truncation_size, X}` option. In rebar, you can add it to +`erl_opts`: ```erlang {erl_opts, [{parse_transform, lager_transform}, {lager_truncation_size, 1024}]}. ``` -You can also pass it to erlc, if you prefer: +You can also pass it to `erlc`, if you prefer: ``` erlc -pa lager/ebin +'{parse_transform, lager_transform}' +'{lager_truncation_size, 1024}' file.erl ``` + +3.x Changelog +------------- +3.1.0 - 27 January 2016 + + * Feature: API calls to a rotate handler, sink or all. This change + introduces a new `rotate` message for 3rd party lager backends; that's + why this is released as a new minor version number. (#311) + +3.0.3 - 27 January 2016 + + * Feature: Pretty printer for human readable stack traces (#298) + * Feature: Make error reformatting optional (#305) + * Feature: Optional and explicit sink for error_logger messages (#303) + * Bugfix: Always explicitly close a file after its been rotated (#316) + * Bugfix: If a relative path already contains the log root, do not add it again (#317) + * Bugfix: Configure and start extra sinks before traces are evaluated (#307) + * Bugfix: Stop and remove traces correctly (#306) + * Bugfix: A byte value of 255 is valid for Unicode (#300) + * Dependency: Bump to goldrush 0.1.8 (#313)
View file
lager-2.1.0.tar.gz/include/lager.hrl -> lager-3.1.0.tar.gz/include/lager.hrl
Changed
@@ -17,10 +17,18 @@ -define(DEFAULT_TRUNCATION, 4096). -define(DEFAULT_TRACER, lager_default_tracer). +-define(DEFAULT_SINK, lager_event). +-define(ERROR_LOGGER_SINK, error_logger_lager_event). + -define(LEVELS, [debug, info, notice, warning, error, critical, alert, emergency, none]). +%% Use of these "functions" means that the argument list will not be +%% truncated for safety +-define(LEVELS_UNSAFE, + [{debug_unsafe, debug}, {info_unsafe, info}, {notice_unsafe, notice}, {warning_unsafe, warning}, {error_unsafe, error}, {critical_unsafe, critical}, {alert_unsafe, alert}, {emergency_unsafe, emergency}]). + -define(DEBUG, 128). -define(INFO, 64). -define(NOTICE, 32). @@ -55,6 +63,9 @@ ?EMERGENCY -> emergency end). +-define(SHOULD_LOG(Sink, Level), + (lager_util:level_to_num(Level) band element(1, lager_config:get({Sink, loglevel}, {?LOG_NONE, []}))) /= 0). + -define(SHOULD_LOG(Level), (lager_util:level_to_num(Level) band element(1, lager_config:get(loglevel, {?LOG_NONE, []}))) /= 0). @@ -63,7 +74,7 @@ Level, [{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}], [])} - )). + )). %% FOR INTERNAL USE ONLY %% internal non-blocking logging call @@ -100,3 +111,15 @@ end)). -endif. +-record(lager_shaper, { + %% how many messages per second we try to deliver + hwm = undefined :: 'undefined' | pos_integer(), + %% how many messages we've received this second + mps = 0 :: non_neg_integer(), + %% the current second + lasttime = os:timestamp() :: erlang:timestamp(), + %% count of dropped messages this second + dropped = 0 :: non_neg_integer() + }). + +-type lager_shaper() :: #lager_shaper{}.
View file
lager-2.1.0.tar.gz/rebar.config -> lager-3.1.0.tar.gz/rebar.config
Changed
@@ -1,9 +1,51 @@ -{erl_opts, [debug_info, warn_untyped_record]}. +%% -*- erlang -*- +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2011-2015 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +{erl_opts, [ + {lager_extra_sinks, ['__lager_test_sink']}, + debug_info, + report, + verbose, + warn_deprecated_function, + warn_deprecated_type, + warn_export_all, + warn_export_vars, + warn_obsolete_guard, + warn_untyped_record, + warn_unused_import + % do NOT include warnings_as_errors, as rebar includes these options + % when compiling for eunit, and at least one test module has code that + % is deliberatly broken and will generate an un-maskable warning +]}. + {erl_first_files, ["src/lager_util.erl"]}. + +{eunit_opts, [verbose]}. +{eunit_compile_opts, [ + nowarn_untyped_record, + nowarn_export_all +]}. {deps, [ - {goldrush, "0\.1\.6", - {git, "git://github.com/DeadZen/goldrush.git", {tag, "0.1.6"}}} - ]}. + {goldrush, ".*", {git, "git://github.com/DeadZen/goldrush.git", {tag, "0.1.8"}}} +]}. {xref_checks, []}. {xref_queries, [{"(XC - UC) || (XU - X - B - lager_default_tracer : Mod - erlang:\"(is_map|map_size)\"/1 - maps:to_list/1)", []}]}.
View file
lager-2.1.0.tar.gz/src/error_logger_lager_h.erl -> lager-3.1.0.tar.gz/src/error_logger_lager_h.erl
Changed
@@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2011-2015 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -31,31 +31,28 @@ -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3]). --export([format_reason/1]). - --record(state, { - %% how many messages per second we try to deliver - hwm = undefined :: 'undefined' | pos_integer(), - %% how many messages we've received this second - mps = 0 :: non_neg_integer(), - %% the current second - lasttime = os:timestamp() :: erlang:timestamp(), - %% count of dropped messages this second - dropped = 0 :: non_neg_integer() +-export([format_reason/1, format_mfa/1, format_args/3]). + +-record(state, { + sink :: atom(), + shaper :: lager_shaper(), + %% group leader strategy + groupleader_strategy :: handle | ignore | mirror, + raw :: boolean() }). --define(LOGMSG(Level, Pid, Msg), - case ?SHOULD_LOG(Level) of +-define(LOGMSG(Sink, Level, Pid, Msg), + case ?SHOULD_LOG(Sink, Level) of true -> - _ =lager:log(Level, Pid, Msg), + _ =lager:log(Sink, Level, Pid, Msg, []), ok; _ -> ok end). --define(LOGFMT(Level, Pid, Fmt, Args), - case ?SHOULD_LOG(Level) of +-define(LOGFMT(Sink, Level, Pid, Fmt, Args), + case ?SHOULD_LOG(Sink, Level) of true -> - _ = lager:log(Level, Pid, Fmt, Args), + _ = lager:log(Sink, Level, Pid, Fmt, Args), ok; _ -> ok end). @@ -74,20 +71,29 @@ gen_event:call(error_logger, ?MODULE, {set_high_water, N}, infinity). -spec init(any()) -> {ok, #state{}}. -init([HighWaterMark]) -> - {ok, #state{hwm=HighWaterMark}}. - -handle_call({set_high_water, N}, State) -> - {ok, ok, State#state{hwm = N}}; +init([HighWaterMark, GlStrategy]) -> + Shaper = #lager_shaper{hwm=HighWaterMark}, + Raw = application:get_env(lager, error_logger_format_raw, false), + Sink = configured_sink(), + {ok, #state{sink=Sink, shaper=Shaper, groupleader_strategy=GlStrategy, raw=Raw}}. + +handle_call({set_high_water, N}, #state{shaper=Shaper} = State) -> + NewShaper = Shaper#lager_shaper{hwm=N}, + {ok, ok, State#state{shaper = NewShaper}}; handle_call(_Request, State) -> {ok, unknown_call, State}. -handle_event(Event, State) -> - case check_hwm(State) of - {true, NewState} -> - log_event(Event, NewState); - {false, NewState} -> - {ok, NewState} +handle_event(Event, #state{sink=Sink, shaper=Shaper} = State) -> + case lager_util:check_hwm(Shaper) of + {true, 0, NewShaper} -> + eval_gl(Event, State#state{shaper=NewShaper}); + {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} when Drop > 0 -> + ?LOGFMT(Sink, warning, self(), + "lager_error_logger_h dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", + [Drop, Hwm]), + eval_gl(Event, State#state{shaper=NewShaper}); + {false, _, NewShaper} -> + {ok, State#state{shaper=NewShaper}} end. handle_info(_Info, State) -> @@ -96,94 +102,96 @@ terminate(_Reason, _State) -> ok. + +code_change(_OldVsn, {state, Shaper, GLStrategy}, _Extra) -> + Raw = application:get_env(lager, error_logger_format_raw, false), + {ok, #state{ + sink=configured_sink(), + shaper=Shaper, + groupleader_strategy=GLStrategy, + raw=Raw + }}; +code_change(_OldVsn, {state, Sink, Shaper, GLS}, _Extra) -> + Raw = application:get_env(lager, error_logger_format_raw, false), + {ok, #state{sink=Sink, shaper=Shaper, groupleader_strategy=GLS, raw=Raw}}; code_change(_OldVsn, State, _Extra) -> {ok, State}. %% internal functions -check_hwm(State = #state{hwm = undefined}) -> - {true, State}; -check_hwm(State = #state{mps = Mps, hwm = Hwm}) when Mps < Hwm -> - %% haven't hit high water mark yet, just log it - {true, State#state{mps=Mps+1}}; -check_hwm(State = #state{hwm = Hwm, lasttime = Last, dropped = Drop}) -> - %% are we still in the same second? - {M, S, _} = Now = os:timestamp(), - case Last of - {M, S, _} -> - %% still in same second, but have exceeded the high water mark - NewDrops = discard_messages(Now, 0), - {false, State#state{dropped=Drop+NewDrops}}; - _ -> - %% different second, reset all counters and allow it - case Drop > 0 of - true -> - ?LOGFMT(warning, self(), "lager_error_logger_h dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", - [Drop, Hwm]); - false -> - ok - end, - {true, State#state{dropped = 0, mps=1, lasttime = Now}} +configured_sink() -> + case proplists:get_value(?ERROR_LOGGER_SINK, application:get_env(lager, extra_sinks, [])) of + undefined -> ?DEFAULT_SINK; + _ -> ?ERROR_LOGGER_SINK end. -discard_messages(Second, Count) -> - {M, S, _} = os:timestamp(), - case Second of - {M, S, _} -> - receive - %% we only discard gen_event notifications, because - %% otherwise we might discard gen_event internal - %% messages, such as trapped EXITs - {notify, _Event} -> - discard_messages(Second, Count+1); - {_From, _Tag, {sync_notify, _Event}} -> - discard_messages(Second, Count+1) - after 0 -> - Count - end; - _ -> - Count - end. +eval_gl(Event, #state{groupleader_strategy=GlStrategy0}=State) when is_pid(element(2, Event)) -> + case element(2, Event) of + GL when node(GL) =/= node(), GlStrategy0 =:= ignore -> + gen_event:notify({error_logger, node(GL)}, Event), + {ok, State}; + GL when node(GL) =/= node(), GlStrategy0 =:= mirror -> + gen_event:notify({error_logger, node(GL)}, Event), + log_event(Event, State); + _ -> + log_event(Event, State) + end; +eval_gl(Event, State) -> + log_event(Event, State). -log_event(Event, State) -> +log_event(Event, #state{sink=Sink} = State) -> case Event of {error, _GL, {Pid, Fmt, Args}} -> - case Fmt of - "** Generic server "++_ -> + FormatRaw = State#state.raw, + case {FormatRaw, Fmt} of + {false, "** Generic server "++_} -> %% gen_server terminate [Name, _Msg, _State, Reason] = Args, ?CRASH_LOG(Event), - ?LOGFMT(error, Pid, "gen_server ~w terminated with reason: ~s", + ?LOGFMT(Sink, error, Pid, "gen_server ~w terminated with reason: ~s", [Name, format_reason(Reason)]); - "** State machine "++_ -> + {false, "** State machine "++_} -> %% gen_fsm terminate [Name, _Msg, StateName, _StateData, Reason] = Args, ?CRASH_LOG(Event), - ?LOGFMT(error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s", + ?LOGFMT(Sink, error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s", [Name, StateName, format_reason(Reason)]); - "** gen_event handler"++_ -> + {false, "** gen_event handler"++_} -> %% gen_event handler terminate [ID, Name, _Msg, _State, Reason] = Args, ?CRASH_LOG(Event), - ?LOGFMT(error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s", + ?LOGFMT(Sink, error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s", [ID, Name, format_reason(Reason)]); - "** Cowboy handler"++_ -> + {false, "** Cowboy handler"++_} -> %% Cowboy HTTP server error ?CRASH_LOG(Event), case Args of [Module, Function, Arity, _Request, _State] -> %% we only get the 5-element list when its a non-exported function - ?LOGFMT(error, Pid, + ?LOGFMT(Sink, error, Pid, "Cowboy handler ~p terminated with reason: call to undefined function ~p:~p/~p", [Module, Module, Function, Arity]); [Module, Function, Arity, _Class, Reason | Tail] -> %% any other cowboy error_format list *always* ends with the stacktrace StackTrace = lists:last(Tail), - ?LOGFMT(error, Pid, + ?LOGFMT(Sink, error, Pid, "Cowboy handler ~p terminated in ~p:~p/~p with reason: ~s", [Module, Module, Function, Arity, format_reason({Reason, StackTrace})]) end; - "webmachine error"++_ -> + {false, "Ranch listener "++_} -> + %% Ranch errors + ?CRASH_LOG(Event), + case Args of + [Ref, _Protocol, Worker, {[{reason, Reason}, {mfa, {Module, Function, Arity}}, {stacktrace, StackTrace} | _], _}] -> + ?LOGFMT(Sink, error, Worker, + "Ranch listener ~p terminated in ~p:~p/~p with reason: ~s", + [Ref, Module, Function, Arity, format_reason({Reason, StackTrace})]); + [Ref, _Protocol, Worker, Reason] -> + ?LOGFMT(Sink, error, Worker, + "Ranch listener ~p terminated with reason: ~s", + [Ref, format_reason(Reason)]) + end; + {false, "webmachine error"++_} -> %% Webmachine HTTP server error ?CRASH_LOG(Event), [Path, Error] = Args, @@ -194,61 +202,71 @@ _ -> Error end, - ?LOGFMT(error, Pid, "Webmachine error at path ~p : ~s", [Path, format_reason(StackTrace)]); + ?LOGFMT(Sink, error, Pid, "Webmachine error at path ~p : ~s", [Path, format_reason(StackTrace)]); _ -> ?CRASH_LOG(Event), - ?LOGMSG(error, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION)) + ?LOGFMT(Sink, error, Pid, Fmt, Args) end; {error_report, _GL, {Pid, std_error, D}} -> ?CRASH_LOG(Event), - ?LOGMSG(error, Pid, print_silly_list(D)); + ?LOGMSG(Sink, error, Pid, print_silly_list(D)); {error_report, _GL, {Pid, supervisor_report, D}} -> ?CRASH_LOG(Event), case lists:sort(D) of [{errorContext, Ctx}, {offender, Off}, {reason, Reason}, {supervisor, Name}] -> Offender = format_offender(Off), - ?LOGFMT(error, Pid, + ?LOGFMT(Sink, error, Pid, "Supervisor ~w had child ~s exit with reason ~s in context ~w", [supervisor_name(Name), Offender, format_reason(Reason), Ctx]); _ -> - ?LOGMSG(error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D)) + ?LOGMSG(Sink, error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D)) end; {error_report, _GL, {Pid, crash_report, [Self, Neighbours]}} -> ?CRASH_LOG(Event), - ?LOGMSG(error, Pid, "CRASH REPORT " ++ format_crash_report(Self, Neighbours)); + ?LOGMSG(Sink, error, Pid, "CRASH REPORT " ++ format_crash_report(Self, Neighbours)); {warning_msg, _GL, {Pid, Fmt, Args}} -> - ?LOGMSG(warning, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION)); + ?LOGFMT(Sink, warning, Pid, Fmt, Args); {warning_report, _GL, {Pid, std_warning, Report}} -> - ?LOGMSG(warning, Pid, print_silly_list(Report)); + ?LOGMSG(Sink, warning, Pid, print_silly_list(Report)); {info_msg, _GL, {Pid, Fmt, Args}} -> - ?LOGMSG(info, Pid, lager:safe_format(Fmt, Args, ?DEFAULT_TRUNCATION)); + ?LOGFMT(Sink, info, Pid, Fmt, Args); {info_report, _GL, {Pid, std_info, D}} when is_list(D) -> Details = lists:sort(D), case Details of [{application, App}, {exited, Reason}, {type, _Type}] -> - ?LOGFMT(info, Pid, "Application ~w exited with reason: ~s", - [App, format_reason(Reason)]); + case application:get_env(lager, suppress_application_start_stop) of + {ok, true} when Reason == stopped -> + ok; + _ -> + ?LOGFMT(Sink, info, Pid, "Application ~w exited with reason: ~s", + [App, format_reason(Reason)]) + end; _ -> - ?LOGMSG(info, Pid, print_silly_list(D)) + ?LOGMSG(Sink, info, Pid, print_silly_list(D)) end; {info_report, _GL, {Pid, std_info, D}} -> - ?LOGFMT(info, Pid, "~w", [D]); + ?LOGFMT(Sink, info, Pid, "~w", [D]); {info_report, _GL, {P, progress, D}} -> Details = lists:sort(D), case Details of [{application, App}, {started_at, Node}] -> - ?LOGFMT(info, P, "Application ~w started on node ~w", - [App, Node]); + case application:get_env(lager, suppress_application_start_stop) of + {ok, true} -> + ok; + _ -> + ?LOGFMT(Sink, info, P, "Application ~w started on node ~w", + [App, Node]) + end; [{started, Started}, {supervisor, Name}] -> MFA = format_mfa(get_value(mfargs, Started)), Pid = get_value(pid, Started), - ?LOGFMT(debug, P, "Supervisor ~w started ~s at pid ~w", + ?LOGFMT(Sink, debug, P, "Supervisor ~w started ~s at pid ~w", [supervisor_name(Name), MFA, Pid]); _ -> - ?LOGMSG(info, P, "PROGRESS REPORT " ++ print_silly_list(D)) + ?LOGMSG(Sink, info, P, "PROGRESS REPORT " ++ print_silly_list(D)) end; _ -> - ?LOGFMT(warning, self(), "Unexpected error_logger event ~w", [Event]) + ?LOGFMT(Sink, warning, self(), "Unexpected error_logger event ~w", [Event]) end, {ok, State}. @@ -308,7 +326,7 @@ format_reason({if_clause, [MFA|_]}) -> ["no true branch found while evaluating if expression in ", format_mfa(MFA)]; format_reason({{try_clause, Val}, [MFA|_]}) -> - ["no try clause matching ", print_val(Val), " in ", format_mfa(MFA)]; + ["no try clause matching ", print_val(Val), " in ", format_mfa(MFA)]; format_reason({badarith, [MFA|_]}) -> ["bad arithmetic expression in ", format_mfa(MFA)]; format_reason({{badmatch, Val}, [MFA|_]}) -> @@ -343,6 +361,8 @@ %% seems to be generated by a bad call to a BIF ["bad argument in ", format_mfa(MFA)] end; +format_reason({{badarg, Stack}, _}) -> + format_reason({badarg, Stack}); format_reason({{badarity, {Fun, Args}}, [MFA|_]}) -> {arity, Arity} = lists:keyfind(arity, 1, erlang:fun_info(Fun)), [io_lib:format("fun called with wrong arity of ~w instead of ~w in ",
View file
lager-2.1.0.tar.gz/src/lager.app.src -> lager-3.1.0.tar.gz/src/lager.app.src
Changed
@@ -3,7 +3,7 @@ {application, lager, [ {description, "Erlang logging framework"}, - {vsn, "2.1.0"}, + {vsn, "3.1.0"}, {modules, []}, {applications, [ kernel, @@ -13,9 +13,9 @@ {registered, [lager_sup, lager_event, lager_crash_log, lager_handler_watcher_sup]}, {mod, {lager_app, []}}, {env, [ - %% Note: application:start(lager) overwrites previously defined environment variables + %% Note: application:start(lager) overwrites previously defined environment variables %% thus declaration of default handlers is done at lager_app.erl - + %% What colors to use with what log levels {colored, false}, {colors, [ @@ -43,14 +43,18 @@ %% Number of rotated crash logs to keep, 0 means keep only the %% current one - default is 0 {crash_log_count, 5}, - %% Whether to redirect error_logger messages into lager - defaults to true + %% Whether to redirect error_logger messages into the default lager_event sink - defaults to true {error_logger_redirect, true}, %% How many messages per second to allow from error_logger before we start dropping them {error_logger_hwm, 50}, - %% How big the gen_event mailbox can get before it is switched into sync mode + %% How big the gen_event mailbox can get before it is + %% switched into sync mode. This value only applies to + %% the default sink; extra sinks can supply their own. {async_threshold, 20}, - %% Switch back to async mode, when gen_event mailbox size decrease from `async_threshold' - %% to async_threshold - async_threshold_window + %% Switch back to async mode, when gen_event mailbox size + %% decrease from `async_threshold' to async_threshold - + %% async_threshold_window. This value only applies to the + %% default sink; extra sinks can supply their own. {async_threshold_window, 5} ]} ]}.
View file
lager-2.1.0.tar.gz/src/lager.erl -> lager-3.1.0.tar.gz/src/lager.erl
Changed
@@ -21,17 +21,21 @@ -include("lager.hrl"). -define(LAGER_MD_KEY, '__lager_metadata'). +-define(TRACE_SINK, '__trace_sink'). +-define(ROTATE_TIMEOUT, 100000). %% API -export([start/0, - log/3, log/4, + log/3, log/4, log/5, + log_unsafe/4, md/0, md/1, + rotate_handler/1, rotate_handler/2, rotate_sink/1, rotate_all/0, trace/2, trace/3, trace_file/2, trace_file/3, trace_file/4, trace_console/1, trace_console/2, - clear_all_traces/0, stop_trace/1, status/0, - get_loglevel/1, set_loglevel/2, set_loglevel/3, get_loglevels/0, - update_loglevel_config/0, posix_error/1, - safe_format/3, safe_format_chop/3, dispatch_log/5, dispatch_log/9, - do_log/9, pr/2]). + list_all_sinks/0, clear_all_traces/0, stop_trace/1, stop_trace/3, status/0, + get_loglevel/1, get_loglevel/2, set_loglevel/2, set_loglevel/3, set_loglevel/4, get_loglevels/1, + update_loglevel_config/1, posix_error/1, set_loghwm/2, set_loghwm/3, set_loghwm/4, + safe_format/3, safe_format_chop/3, unsafe_format/2, dispatch_log/5, dispatch_log/7, dispatch_log/9, + do_log/9, do_log/10, do_log_unsafe/10, pr/2, pr/3, pr_stacktrace/1, pr_stacktrace/2]). -type log_level() :: debug | info | notice | warning | error | critical | alert | emergency. -type log_level_number() :: 0..7. @@ -81,51 +85,85 @@ md(_) -> erlang:error(badarg). --spec dispatch_log(log_level(), list(), string(), list() | none, pos_integer()) -> ok | {error, lager_not_running}. + +-spec dispatch_log(atom(), log_level(), list(), string(), list() | none, pos_integer(), safe | unsafe) -> ok | {error, lager_not_running} | {error, {sink_not_configured, atom()}}. %% this is the same check that the parse transform bakes into the module at compile time -dispatch_log(Severity, Metadata, Format, Args, Size) when is_atom(Severity)-> +%% see lager_transform (lines 173-216) +dispatch_log(Sink, Severity, Metadata, Format, Args, Size, Safety) when is_atom(Severity)-> SeverityAsInt=lager_util:level_to_num(Severity), - case {whereis(lager_event), lager_config:get(loglevel, {?LOG_NONE, []})} of - {undefined, _} -> - {error, lager_not_running}; - {Pid, {Level, Traces}} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] -> - do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Pid); - _ -> - ok + case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of + {undefined, undefined, _} -> {error, lager_not_running}; + {undefined, _LagerEventPid0, _} -> {error, {sink_not_configured, Sink}}; + {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= safe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) -> + do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid); + {SinkPid, _LagerEventPid1, {Level, Traces}} when Safety =:= unsafe andalso ( (Level band SeverityAsInt) /= 0 orelse Traces /= [] ) -> + do_log_unsafe(Severity, Metadata, Format, Args, Size, SeverityAsInt, Level, Traces, Sink, SinkPid); + _ -> ok end. %% @private Should only be called externally from code generated from the parse transform -do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, Pid) when is_atom(Severity) -> - Destinations = case TraceFilters of +do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) -> + FormatFun = fun() -> safe_format_chop(Format, Args, Size) end, + do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun). + +do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun) -> + {Destinations, TraceSinkPid} = case TraceFilters of [] -> - []; + {[], undefined}; _ -> - lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[]) + {lager_util:check_traces(Metadata,SeverityAsInt,TraceFilters,[]), whereis(?TRACE_SINK)} end, case (LevelThreshold band SeverityAsInt) /= 0 orelse Destinations /= [] of true -> Msg = case Args of A when is_list(A) -> - safe_format_chop(Format,Args,Size); + FormatFun(); _ -> Format end, LagerMsg = lager_msg:new(Msg, Severity, Metadata, Destinations), - case lager_config:get(async, false) of + case lager_config:get({Sink, async}, false) of true -> - gen_event:notify(Pid, {log, LagerMsg}); + gen_event:notify(SinkPid, {log, LagerMsg}); false -> - gen_event:sync_notify(Pid, {log, LagerMsg}) + gen_event:sync_notify(SinkPid, {log, LagerMsg}) + end, + case TraceSinkPid /= undefined of + true -> + gen_event:notify(TraceSinkPid, {log, LagerMsg}); + false -> + ok end; false -> ok end. +%% @private Should only be called externally from code generated from the parse transform +%% Specifically, it would be level ++ `_unsafe' as in `info_unsafe'. +do_log_unsafe(Severity, Metadata, Format, Args, _Size, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid) when is_atom(Severity) -> + FormatFun = fun() -> unsafe_format(Format, Args) end, + do_log_impl(Severity, Metadata, Format, Args, SeverityAsInt, LevelThreshold, TraceFilters, Sink, SinkPid, FormatFun). + + %% backwards compatible with beams compiled with lager 1.x dispatch_log(Severity, _Module, _Function, _Line, _Pid, Metadata, Format, Args, Size) -> dispatch_log(Severity, Metadata, Format, Args, Size). +%% backwards compatible with beams compiled with lager 2.x +dispatch_log(Severity, Metadata, Format, Args, Size) -> + dispatch_log(?DEFAULT_SINK, Severity, Metadata, Format, Args, Size, safe). + +%% backwards compatible with beams compiled with lager 2.x +do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, LevelThreshold, TraceFilters, SinkPid) -> + do_log(Severity, Metadata, Format, Args, Size, SeverityAsInt, + LevelThreshold, TraceFilters, ?DEFAULT_SINK, SinkPid). + + +%% TODO: +%% Consider making log2/4 that takes the Level, Pid and Message params of log/3 +%% along with a Sink param?? + %% @doc Manually log a message into lager without using the parse transform. -spec log(log_level(), pid() | atom() | [tuple(),...], list()) -> ok | {error, lager_not_running}. log(Level, Pid, Message) when is_pid(Pid); is_atom(Pid) -> @@ -140,6 +178,27 @@ log(Level, Metadata, Format, Args) when is_list(Metadata) -> dispatch_log(Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION). +log_unsafe(Level, Metadata, Format, Args) when is_list(Metadata) -> + dispatch_log(?DEFAULT_SINK, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, unsafe). + + +%% @doc Manually log a message into lager without using the parse transform. +-spec log(atom(), log_level(), pid() | atom() | [tuple(),...], string(), list()) -> ok | {error, lager_not_running}. +log(Sink, Level, Pid, Format, Args) when is_pid(Pid); is_atom(Pid) -> + dispatch_log(Sink, Level, [{pid,Pid}], Format, Args, ?DEFAULT_TRUNCATION, safe); +log(Sink, Level, Metadata, Format, Args) when is_list(Metadata) -> + dispatch_log(Sink, Level, Metadata, Format, Args, ?DEFAULT_TRUNCATION, safe). + +validate_trace_filters(Filters, Level, Backend) -> + Sink = proplists:get_value(sink, Filters, ?DEFAULT_SINK), + {Sink, + lager_util:validate_trace({ + proplists:delete(sink, Filters), + Level, + Backend + }) + }. + trace_file(File, Filter) -> trace_file(File, Filter, debug, []). @@ -148,34 +207,43 @@ trace_file(File, Filter, Options) when is_list(Options) -> trace_file(File, Filter, debug, Options). - + trace_file(File, Filter, Level, Options) -> - Trace0 = {Filter, Level, {lager_file_backend, File}}, - case lager_util:validate_trace(Trace0) of - {ok, Trace} -> - Handlers = gen_event:which_handlers(lager_event), + FileName = lager_util:expand_path(File), + case validate_trace_filters(Filter, Level, {lager_file_backend, FileName}) of + {Sink, {ok, Trace}} -> + Handlers = lager_config:global_get(handlers, []), %% check if this file backend is already installed - Res = case lists:member({lager_file_backend, File}, Handlers) of - false -> - %% install the handler - LogFileConfig = lists:keystore(level, 1, lists:keystore(file, 1, Options, {file, File}), {level, none}), - supervisor:start_child(lager_handler_watcher_sup, - [lager_event, {lager_file_backend, File}, LogFileConfig]); - _ -> - {ok, exists} + Res = case lists:keyfind({lager_file_backend, FileName}, 1, Handlers) of + false -> + %% install the handler + LogFileConfig = + lists:keystore(level, 1, + lists:keystore(file, 1, + Options, + {file, FileName}), + {level, none}), + HandlerInfo = + lager_app:start_handler(Sink, {lager_file_backend, FileName}, + LogFileConfig), + lager_config:global_set(handlers, [HandlerInfo|Handlers]), + {ok, installed}; + {_Watcher, _Handler, Sink} -> + {ok, exists}; + {_Watcher, _Handler, _OtherSink} -> + {error, file_in_use} end, case Res of {ok, _} -> - add_trace_to_loglevel_config(Trace), - {ok, Trace}; + add_trace_to_loglevel_config(Trace, Sink), + {ok, {{lager_file_backend, FileName}, Filter, Level}}; {error, _} = E -> E end; - Error -> + {_Sink, Error} -> Error end. - trace_console(Filter) -> trace_console(Filter, debug). @@ -185,28 +253,53 @@ trace(Backend, Filter) -> trace(Backend, Filter, debug). +trace({lager_file_backend, File}, Filter, Level) -> + trace_file(File, Filter, Level); + trace(Backend, Filter, Level) -> - Trace0 = {Filter, Level, Backend}, - case lager_util:validate_trace(Trace0) of - {ok, Trace} -> - add_trace_to_loglevel_config(Trace), - {ok, Trace}; - Error -> + case validate_trace_filters(Filter, Level, Backend) of + {Sink, {ok, Trace}} -> + add_trace_to_loglevel_config(Trace, Sink), + {ok, {Backend, Filter, Level}}; + {_Sink, Error} -> + Error + end. + +stop_trace(Backend, Filter, Level) -> + case validate_trace_filters(Filter, Level, Backend) of + {Sink, {ok, Trace}} -> + stop_trace_int(Trace, Sink); + {_Sink, Error} -> Error end. -stop_trace({_Filter, _Level, Target} = Trace) -> - {Level, Traces} = lager_config:get(loglevel), +stop_trace({Backend, Filter, Level}) -> + stop_trace(Backend, Filter, Level). + +%% Important: validate_trace_filters orders the arguments of +%% trace tuples differently than the way outside callers have +%% the trace tuple. +%% +%% That is to say, outside they are represented as +%% `{Backend, Filter, Level}' +%% +%% and when they come back from validation, they're +%% `{Filter, Level, Backend}' +stop_trace_int({_Filter, _Level, Backend} = Trace, Sink) -> + {Level, Traces} = lager_config:get({Sink, loglevel}), NewTraces = lists:delete(Trace, Traces), _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces ]), %MinLevel = minimum_loglevel(get_loglevels() ++ get_trace_levels(NewTraces)), - lager_config:set(loglevel, {Level, NewTraces}), - case get_loglevel(Target) of + lager_config:set({Sink, loglevel}, {Level, NewTraces}), + case get_loglevel(Sink, Backend) of none -> %% check no other traces point here - case lists:keyfind(Target, 3, NewTraces) of + case lists:keyfind(Backend, 3, NewTraces) of false -> - gen_event:delete_handler(lager_event, Target, []); + gen_event:delete_handler(Sink, Backend, []), + lager_config:global_set(handlers, + lists:keydelete(Backend, 1, + lager_config:global_get(handlers))); _ -> ok end; @@ -215,37 +308,69 @@ end, ok. +list_all_sinks() -> + sets:to_list( + lists:foldl(fun({_Watcher, _Handler, Sink}, Set) -> + sets:add_element(Sink, Set) + end, + sets:new(), + lager_config:global_get(handlers, []))). + +clear_traces_by_sink(Sinks) -> + lists:foreach(fun(S) -> + {Level, _Traces} = + lager_config:get({S, loglevel}), + lager_config:set({S, loglevel}, + {Level, []}) + end, + Sinks). + clear_all_traces() -> - {Level, _Traces} = lager_config:get(loglevel), + Handlers = lager_config:global_get(handlers, []), + clear_traces_by_sink(list_all_sinks()), _ = lager_util:trace_filter(none), - lager_config:set(loglevel, {Level, []}), - lists:foreach(fun(Handler) -> - case get_loglevel(Handler) of - none -> - gen_event:delete_handler(lager_event, Handler, []); - _ -> - ok - end - end, gen_event:which_handlers(lager_event)). + lager_config:global_set(handlers, + lists:filter( + fun({Handler, _Watcher, Sink}) -> + case get_loglevel(Sink, Handler) of + none -> + gen_event:delete_handler(Sink, Handler, []), + false; + _ -> + true + end + end, Handlers)). + +find_traces(Sinks) -> + lists:foldl(fun(S, Acc) -> + {_Level, Traces} = lager_config:get({S, loglevel}), + Acc ++ lists:map(fun(T) -> {S, T} end, Traces) + end, + [], + Sinks). status() -> - Handlers = gen_event:which_handlers(lager_event), - TraceCount = case length(element(2, lager_config:get(loglevel))) of + Handlers = lager_config:global_get(handlers, []), + Sinks = lists:sort(list_all_sinks()), + Traces = find_traces(Sinks), + TraceCount = case length(Traces) of 0 -> 1; N -> N end, Status = ["Lager status:\n", [begin - Level = get_loglevel(Handler), + Level = get_loglevel(Sink, Handler), case Handler of {lager_file_backend, File} -> - io_lib:format("File ~s at level ~p\n", [File, Level]); + io_lib:format("File ~s (~s) at level ~p\n", [File, Sink, Level]); lager_console_backend -> - io_lib:format("Console at level ~p\n", [Level]); + io_lib:format("Console (~s) at level ~p\n", [Sink, Level]); _ -> [] end - end || Handler <- Handlers], + end || {Handler, _Watcher, Sink} <- lists:sort(fun({_, _, S1}, + {_, _, S2}) -> S1 =< S2 end, + Handlers)], "Active Traces:\n", [begin LevelName = case Level of @@ -257,9 +382,9 @@ Num -> lager_util:num_to_level(Num) end, - io_lib:format("Tracing messages matching ~p at level ~p to ~p\n", - [Filter, LevelName, Destination]) - end || {Filter, Level, Destination} <- element(2, lager_config:get(loglevel))], + io_lib:format("Tracing messages matching ~p (sink ~s) at level ~p to ~p\n", + [Filter, Sink, LevelName, Destination]) + end || {Sink, {Filter, Level, Destination}} <- Traces], [ "Tracing Reductions:\n", case ?DEFAULT_TRACER:info('query') of @@ -269,7 +394,7 @@ ], [ "Tracing Statistics:\n ", - [ begin + [ begin [" ", atom_to_list(Table), ": ", integer_to_list(?DEFAULT_TRACER:info(Table) div TraceCount), "\n"] @@ -280,21 +405,34 @@ %% @doc Set the loglevel for a particular backend. set_loglevel(Handler, Level) when is_atom(Level) -> - Reply = gen_event:call(lager_event, Handler, {set_loglevel, Level}, infinity), - update_loglevel_config(), - Reply. + set_loglevel(?DEFAULT_SINK, Handler, undefined, Level). %% @doc Set the loglevel for a particular backend that has multiple identifiers %% (eg. the file backend). set_loglevel(Handler, Ident, Level) when is_atom(Level) -> - Reply = gen_event:call(lager_event, {Handler, Ident}, {set_loglevel, Level}, infinity), - update_loglevel_config(), + set_loglevel(?DEFAULT_SINK, Handler, Ident, Level). + +%% @doc Set the loglevel for a particular sink's backend that potentially has +%% multiple identifiers. (Use `undefined' if it doesn't have any.) +set_loglevel(Sink, Handler, Ident, Level) when is_atom(Level) -> + HandlerArg = case Ident of + undefined -> Handler; + _ -> {Handler, Ident} + end, + Reply = gen_event:call(Sink, HandlerArg, {set_loglevel, Level}, infinity), + update_loglevel_config(Sink), Reply. -%% @doc Get the loglevel for a particular backend. In the case that the backend -%% has multiple identifiers, the lowest is returned + +%% @doc Get the loglevel for a particular backend on the default sink. In the case that the backend +%% has multiple identifiers, the lowest is returned. get_loglevel(Handler) -> - case gen_event:call(lager_event, Handler, get_loglevel, infinity) of + get_loglevel(?DEFAULT_SINK, Handler). + +%% @doc Get the loglevel for a particular sink's backend. In the case that the backend +%% has multiple identifiers, the lowest is returned. +get_loglevel(Sink, Handler) -> + case gen_event:call(Sink, Handler, get_loglevel, infinity) of {mask, Mask} -> case lager_util:mask_to_levels(Mask) of [] -> none; @@ -316,27 +454,43 @@ safe_format_chop("~p", [Error], ?DEFAULT_TRUNCATION). %% @private -get_loglevels() -> - [gen_event:call(lager_event, Handler, get_loglevel, infinity) || - Handler <- gen_event:which_handlers(lager_event)]. +get_loglevels(Sink) -> + [gen_event:call(Sink, Handler, get_loglevel, infinity) || + Handler <- gen_event:which_handlers(Sink)]. + +%% @doc Set the loghwm for the default sink. +set_loghwm(Handler, Hwm) when is_integer(Hwm) -> + set_loghwm(?DEFAULT_SINK, Handler, Hwm). + +%% @doc Set the loghwm for a particular backend. +set_loghwm(Sink, Handler, Hwm) when is_integer(Hwm) -> + gen_event:call(Sink, Handler, {set_loghwm, Hwm}, infinity). + +%% @doc Set the loghwm (log high water mark) for file backends with multiple identifiers +set_loghwm(Sink, Handler, Ident, Hwm) when is_integer(Hwm) -> + gen_event:call(Sink, {Handler, Ident}, {set_loghwm, Hwm}, infinity). %% @private -add_trace_to_loglevel_config(Trace) -> - {MinLevel, Traces} = lager_config:get(loglevel), +add_trace_to_loglevel_config(Trace, Sink) -> + {MinLevel, Traces} = lager_config:get({Sink, loglevel}), case lists:member(Trace, Traces) of false -> NewTraces = [Trace|Traces], _ = lager_util:trace_filter([ element(1, T) || T <- NewTraces]), - lager_config:set(loglevel, {MinLevel, [Trace|Traces]}); + lager_config:set({Sink, loglevel}, {MinLevel, [Trace|Traces]}); _ -> ok end. %% @doc recalculate min log level -update_loglevel_config() -> - {_, Traces} = lager_config:get(loglevel), - MinLog = minimum_loglevel(get_loglevels()), - lager_config:set(loglevel, {MinLog, Traces}). +update_loglevel_config(error_logger) -> + %% Not a sink under our control, part of the Erlang logging + %% utility that error_logger_lager_h attaches to + true; +update_loglevel_config(Sink) -> + {_, Traces} = lager_config:get({Sink, loglevel}, {ignore_me, []}), + MinLog = minimum_loglevel(get_loglevels(Sink)), + lager_config:set({Sink, loglevel}, {MinLog, Traces}). %% @private minimum_loglevel(Levels) -> @@ -367,49 +521,118 @@ safe_format_chop(Fmt, Args, Limit) -> safe_format(Fmt, Args, Limit, [{chomp, true}]). +%% @private Print the format string `Fmt' with `Args' without a size limit. +%% This is unsafe because the output of this function is unbounded. +%% +%% Log messages with unbounded size will kill your application dead as +%% OTP mechanisms stuggle to cope with them. So this function is +%% intended <b>only</b> for messages which have a reasonable bounded +%% size before they're formatted. +%% +%% If the format string is invalid or not enough arguments are +%% supplied a 'FORMAT ERROR' message is printed instead with the +%% offending arguments. The caller is NOT crashed. +unsafe_format(Fmt, Args) -> + try io_lib:format(Fmt, Args) + catch + _:_ -> io_lib:format("FORMAT ERROR: ~p ~p", [Fmt, Args]) + end. + %% @doc Print a record lager found during parse transform pr(Record, Module) when is_tuple(Record), is_atom(element(1, Record)) -> + pr(Record, Module, []); +pr(Record, _) -> + Record. + +%% @doc Print a record lager found during parse transform +pr(Record, Module, Options) when is_tuple(Record), is_atom(element(1, Record)), is_list(Options) -> try case is_record_known(Record, Module) of false -> Record; {RecordName, RecordFields} -> {'$lager_record', RecordName, - zip(RecordFields, tl(tuple_to_list(Record)), Module, [])} + zip(RecordFields, tl(tuple_to_list(Record)), Module, Options, [])} end catch error:undef -> Record end; -pr(Record, _) -> +pr(Record, _, _) -> Record. -zip([FieldName|RecordFields], [FieldValue|Record], Module, ToReturn) -> +zip([FieldName|RecordFields], [FieldValue|Record], Module, Options, ToReturn) -> + Compress = lists:member(compress, Options), case is_tuple(FieldValue) andalso tuple_size(FieldValue) > 0 andalso is_atom(element(1, FieldValue)) andalso is_record_known(FieldValue, Module) of + false when Compress andalso FieldValue =:= undefined -> + zip(RecordFields, Record, Module, Options, ToReturn); false -> - zip(RecordFields, Record, Module, [{FieldName, FieldValue}|ToReturn]); + zip(RecordFields, Record, Module, Options, [{FieldName, FieldValue}|ToReturn]); _Else -> - F = {FieldName, pr(FieldValue, Module)}, - zip(RecordFields, Record, Module, [F|ToReturn]) + F = {FieldName, pr(FieldValue, Module, Options)}, + zip(RecordFields, Record, Module, Options, [F|ToReturn]) end; -zip([], [], _Module, ToReturn) -> +zip([], [], _Module, _Compress, ToReturn) -> lists:reverse(ToReturn). -is_record_known(Record, Module) -> +is_record_known(Record, Module) -> Name = element(1, Record), Attrs = Module:module_info(attributes), case lists:keyfind(lager_records, 1, Attrs) of false -> false; - {lager_records, Records} -> + {lager_records, Records} -> case lists:keyfind(Name, 1, Records) of false -> false; - {Name, RecordFields} -> + {Name, RecordFields} -> case (tuple_size(Record) - 1) =:= length(RecordFields) of false -> false; true -> {Name, RecordFields} end end end. + + +%% @doc Print stacktrace in human readable form +pr_stacktrace(Stacktrace) -> + Indent = "\n ", + lists:foldl( + fun(Entry, Acc) -> + Acc ++ Indent ++ error_logger_lager_h:format_mfa(Entry) + end, + [], + lists:reverse(Stacktrace)). + +pr_stacktrace(Stacktrace, {Class, Reason}) -> + lists:flatten( + pr_stacktrace(Stacktrace) ++ "\n" ++ io_lib:format("~s:~p", [Class, Reason])). + +rotate_sink(Sink) -> + Handlers = lager_config:global_get(handlers), + RotateHandlers = lists:filtermap( + fun({Handler,_,S}) when S == Sink -> {true, {Handler, Sink}}; + (_) -> false + end, + Handlers), + rotate_handlers(RotateHandlers). + +rotate_all() -> + rotate_handlers(lists:map(fun({H,_,S}) -> {H, S} end, + lager_config:global_get(handlers))). + + +rotate_handlers(Handlers) -> + [ rotate_handler(Handler, Sink) || {Handler, Sink} <- Handlers ]. + + +rotate_handler(Handler) -> + Handlers = lager_config:global_get(handlers), + case lists:keyfind(Handler, 1, Handlers) of + {Handler, _, Sink} -> rotate_handler(Handler, Sink); + false -> ok + end. + +rotate_handler(Handler, Sink) -> + gen_event:call(Sink, Handler, rotate, ?ROTATE_TIMEOUT).
View file
lager-2.1.0.tar.gz/src/lager_app.erl -> lager-3.1.0.tar.gz/src/lager_app.erl
Changed
@@ -27,93 +27,204 @@ -endif. -export([start/0, start/2, + start_handler/3, stop/1]). +-define(FILENAMES, '__lager_file_backend_filenames'). +-define(THROTTLE, lager_backend_throttle). +-define(DEFAULT_HANDLER_CONF, + [{lager_console_backend, info}, + {lager_file_backend, + [{file, "log/error.log"}, {level, error}, + {size, 10485760}, {date, "$D0"}, {count, 5}] + }, + {lager_file_backend, + [{file, "log/console.log"}, {level, info}, + {size, 10485760}, {date, "$D0"}, {count, 5}] + } + ]). + start() -> application:start(lager). -start(_StartType, _StartArgs) -> - {ok, Pid} = lager_sup:start_link(), +start_throttle(Sink, Threshold, Window) -> + _ = supervisor:start_child(lager_handler_watcher_sup, + [Sink, ?THROTTLE, [Threshold, Window]]), + ok. +determine_async_behavior(_Sink, {ok, undefined}, _Window) -> + ok; +determine_async_behavior(_Sink, undefined, _Window) -> + ok; +determine_async_behavior(_Sink, {ok, Threshold}, _Window) when not is_integer(Threshold) orelse Threshold < 0 -> + error_logger:error_msg("Invalid value for 'async_threshold': ~p~n", + [Threshold]), + throw({error, bad_config}); +determine_async_behavior(Sink, {ok, Threshold}, undefined) -> + start_throttle(Sink, Threshold, erlang:trunc(Threshold * 0.2)); +determine_async_behavior(_Sink, {ok, Threshold}, {ok, Window}) when not is_integer(Window) orelse Window > Threshold orelse Window < 0 -> + error_logger:error_msg( + "Invalid value for 'async_threshold_window': ~p~n", [Window]), + throw({error, bad_config}); +determine_async_behavior(Sink, {ok, Threshold}, {ok, Window}) -> + start_throttle(Sink, Threshold, Window). - case application:get_env(lager, async_threshold) of - undefined -> - ok; - {ok, undefined} -> - undefined; - {ok, Threshold} when is_integer(Threshold), Threshold >= 0 -> - DefWindow = erlang:trunc(Threshold * 0.2), % maybe 0? - ThresholdWindow = - case application:get_env(lager, async_threshold_window) of +start_handlers(_Sink, undefined) -> + ok; +start_handlers(_Sink, Handlers) when not is_list(Handlers) -> + error_logger:error_msg( + "Invalid value for 'handlers' (must be list): ~p~n", [Handlers]), + throw({error, bad_config}); +start_handlers(Sink, Handlers) -> + %% handlers failing to start are handled in the handler_watcher + lager_config:global_set(handlers, + lager_config:global_get(handlers, []) ++ + lists:map(fun({Module, Config}) -> + check_handler_config(Module, Config), + start_handler(Sink, Module, Config); + (_) -> + throw({error, bad_config}) + end, + expand_handlers(Handlers))), + ok. + +start_handler(Sink, Module, Config) -> + {ok, Watcher} = supervisor:start_child(lager_handler_watcher_sup, + [Sink, Module, Config]), + {Module, Watcher, Sink}. + +check_handler_config({lager_file_backend, F}, Config) when is_list(Config) -> + Fs = case get(?FILENAMES) of + undefined -> ordsets:new(); + X -> X + end, + case ordsets:is_element(F, Fs) of + true -> + error_logger:error_msg( + "Cannot have same file (~p) in multiple file backends~n", [F]), + throw({error, bad_config}); + false -> + put(?FILENAMES, + ordsets:add_element(F, Fs)) + end, + ok; +check_handler_config(_Handler, Config) when is_list(Config) orelse is_atom(Config) -> + ok; +check_handler_config(Handler, _BadConfig) -> + throw({error, {bad_config, Handler}}). + +clean_up_config_checks() -> + erase(?FILENAMES). + +interpret_hwm(undefined) -> + undefined; +interpret_hwm({ok, undefined}) -> + undefined; +interpret_hwm({ok, HWM}) when not is_integer(HWM) orelse HWM < 0 -> + _ = lager:log(warning, self(), "Invalid error_logger high water mark: ~p, disabling", [HWM]), + undefined; +interpret_hwm({ok, HWM}) -> + HWM. + +start_error_logger_handler({ok, false}, _HWM, _Whitelist) -> + []; +start_error_logger_handler(_, HWM, undefined) -> + start_error_logger_handler(ignore_me, HWM, {ok, []}); +start_error_logger_handler(_, HWM, {ok, WhiteList}) -> + GlStrategy = case application:get_env(lager, error_logger_groupleader_strategy) of undefined -> - DefWindow; - {ok, Window} when is_integer(Window), Window < Threshold, Window >= 0 -> - Window; - {ok, BadWindow} -> + handle; + {ok, GlStrategy0} when + GlStrategy0 =:= handle; + GlStrategy0 =:= ignore; + GlStrategy0 =:= mirror -> + GlStrategy0; + {ok, BadGlStrategy} -> error_logger:error_msg( - "Invalid value for 'async_threshold_window': ~p~n", [BadWindow]), + "Invalid value for 'error_logger_groupleader_strategy': ~p~n", + [BadGlStrategy]), throw({error, bad_config}) end, - _ = supervisor:start_child(lager_handler_watcher_sup, - [lager_event, lager_backend_throttle, [Threshold, ThresholdWindow]]), - ok; - {ok, BadThreshold} -> - error_logger:error_msg("Invalid value for 'async_threshold': ~p~n", [BadThreshold]), - throw({error, bad_config}) - end, - Handlers = case application:get_env(lager, handlers) of - undefined -> - [{lager_console_backend, info}, - {lager_file_backend, [{file, "log/error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}]}, - {lager_file_backend, [{file, "log/console.log"}, {level, info}, {size, 10485760}, {date, "$D0"}, {count, 5}]}]; - {ok, Val} -> - Val - end, + case supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, [HWM, GlStrategy]]) of + {ok, _} -> + [begin error_logger:delete_report_handler(X), X end || + X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h | WhiteList]]; + {error, _} -> + [] + end. - %% handlers failing to start are handled in the handler_watcher - _ = [supervisor:start_child(lager_handler_watcher_sup, [lager_event, Module, Config]) || - {Module, Config} <- expand_handlers(Handlers)], +%% `determine_async_behavior/3' is called with the results from either +%% `application:get_env/2' and `proplists:get_value/2'. Since +%% `application:get_env/2' wraps a successful retrieval in an `{ok, +%% Value}' tuple, do the same for the result from +%% `proplists:get_value/2'. +wrap_proplist_value(undefined) -> + undefined; +wrap_proplist_value(Value) -> + {ok, Value}. - ok = add_configured_traces(), +configure_sink(Sink, SinkDef) -> + lager_config:new_sink(Sink), + ChildId = lager_util:make_internal_sink_name(Sink), + _ = supervisor:start_child(lager_sup, + {ChildId, + {gen_event, start_link, + [{local, Sink}]}, + permanent, 5000, worker, dynamic}), + determine_async_behavior(Sink, + wrap_proplist_value( + proplists:get_value(async_threshold, SinkDef)), + wrap_proplist_value( + proplists:get_value(async_threshold_window, SinkDef)) + ), + start_handlers(Sink, + proplists:get_value(handlers, SinkDef, [])), - %% mask the messages we have no use for - lager:update_loglevel_config(), - - HighWaterMark = case application:get_env(lager, error_logger_hwm) of - {ok, undefined} -> - undefined; - {ok, HwmVal} when is_integer(HwmVal), HwmVal > 0 -> - HwmVal; - {ok, BadVal} -> - _ = lager:log(warning, self(), "Invalid error_logger high water mark: ~p, disabling", [BadVal]), - undefined; - undefined -> - undefined - end, + lager:update_loglevel_config(Sink). - SavedHandlers = - case application:get_env(lager, error_logger_redirect) of - {ok, false} -> - []; - _ -> - WhiteList = case application:get_env(lager, error_logger_whitelist) of - undefined -> - []; - {ok, WhiteList0} -> - WhiteList0 - end, - case supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, [HighWaterMark]]) of - {ok, _} -> - [begin error_logger:delete_report_handler(X), X end || - X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h | WhiteList]]; - {error, _} -> - [] - end - end, +configure_extra_sinks(Sinks) -> + lists:foreach(fun({Sink, Proplist}) -> configure_sink(Sink, Proplist) end, + Sinks). + +%% R15 doesn't know about application:get_env/3 +get_env(Application, Key, Default) -> + get_env_default(application:get_env(Application, Key), + Default). + +get_env_default(undefined, Default) -> + Default; +get_env_default({ok, Value}, _Default) -> + Value. + +start(_StartType, _StartArgs) -> + {ok, Pid} = lager_sup:start_link(), + + %% Handle the default sink. + determine_async_behavior(?DEFAULT_SINK, + application:get_env(lager, async_threshold), + application:get_env(lager, async_threshold_window)), + start_handlers(?DEFAULT_SINK, + get_env(lager, handlers, ?DEFAULT_HANDLER_CONF)), + + + lager:update_loglevel_config(?DEFAULT_SINK), + + SavedHandlers = start_error_logger_handler( + application:get_env(lager, error_logger_redirect), + interpret_hwm(application:get_env(lager, error_logger_hwm)), + application:get_env(lager, error_logger_whitelist) + ), - _ = lager_util:trace_filter(none), + _ = lager_util:trace_filter(none), + + %% Now handle extra sinks + configure_extra_sinks(get_env(lager, extra_sinks, [])), + + ok = add_configured_traces(), + + clean_up_config_checks(), {ok, Pid, SavedHandlers}. @@ -153,9 +264,9 @@ maybe_make_handler_id(Mod, Config) -> %% Allow the backend to generate a gen_event handler id, if it wants to. - %% We don't use erlang:function_exported here because that requires the module + %% We don't use erlang:function_exported here because that requires the module %% already be loaded, which is unlikely at this phase of startup. Using code:load - %% caused undesireable side-effects with generating code-coverage reports. + %% caused undesirable side-effects with generating code-coverage reports. try Mod:config_to_id(Config) of Id -> {Id, Config} @@ -229,4 +340,30 @@ ) } ]. + +check_handler_config_test_() -> + Good = expand_handlers(?DEFAULT_HANDLER_CONF), + Bad = expand_handlers([{lager_console_backend, info}, + {lager_file_backend, [{file, "same_file.log"}]}, + {lager_file_backend, [{file, "same_file.log"}, {level, info}]}]), + AlsoBad = [{lager_logstash_backend, + {level, info}, + {output, {udp, "localhost", 5000}}, + {format, json}, + {json_encoder, jiffy}}], + BadToo = [{fail, {fail}}], + [ + {"lager_file_backend_good", + ?_assertEqual([ok, ok, ok], [ check_handler_config(M,C) || {M,C} <- Good ]) + }, + {"lager_file_backend_bad", + ?_assertThrow({error, bad_config}, [ check_handler_config(M,C) || {M,C} <- Bad ]) + }, + {"Invalid config dies", + ?_assertThrow({error, bad_config}, start_handlers(foo, AlsoBad)) + }, + {"Invalid config dies", + ?_assertThrow({error, {bad_config, _}}, start_handlers(foo, BadToo)) + } + ]. -endif.
View file
lager-2.1.0.tar.gz/src/lager_backend_throttle.erl -> lager-3.1.0.tar.gz/src/lager_backend_throttle.erl
Changed
@@ -29,15 +29,27 @@ -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3]). +%% +%% Allow test code to verify that we're doing the needful. +-ifdef(TEST). +-define(ETS_TABLE, async_threshold_test). +-define(TOGGLE_SYNC(), test_increment(sync_toggled)). +-define(TOGGLE_ASYNC(), test_increment(async_toggled)). +-else. +-define(TOGGLE_SYNC(), true). +-define(TOGGLE_ASYNC(), true). +-endif. + -record(state, { + sink :: atom(), hwm :: non_neg_integer(), window_min :: non_neg_integer(), async = true :: boolean() }). -init([Hwm, Window]) -> - lager_config:set(async, true), - {ok, #state{hwm=Hwm, window_min=Hwm - Window}}. +init([{sink, Sink}, Hwm, Window]) -> + lager_config:set({Sink, async}, true), + {ok, #state{sink=Sink, hwm=Hwm, window_min=Hwm - Window}}. handle_call(get_loglevel, State) -> @@ -52,11 +64,13 @@ case {Len > State#state.hwm, Len < State#state.window_min, State#state.async} of {true, _, true} -> %% need to flip to sync mode - lager_config:set(async, false), + ?TOGGLE_SYNC(), + lager_config:set({State#state.sink, async}, false), {ok, State#state{async=false}}; {_, true, false} -> %% need to flip to async mode - lager_config:set(async, true), + ?TOGGLE_ASYNC(), + lager_config:set({State#state.sink, async}, true), {ok, State#state{async=true}}; _ -> %% nothing needs to change @@ -76,3 +90,16 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. +-ifdef(TEST). +test_get(Key) -> + get_default(ets:lookup(?ETS_TABLE, Key)). + +test_increment(Key) -> + ets:insert(?ETS_TABLE, + {Key, test_get(Key) + 1}). + +get_default([]) -> + 0; +get_default([{_Key, Value}]) -> + Value. +-endif.
View file
lager-2.1.0.tar.gz/src/lager_common_test_backend.erl -> lager-3.1.0.tar.gz/src/lager_common_test_backend.erl
Changed
@@ -40,9 +40,15 @@ bounce(Level) -> _ = application:stop(lager), - lager:start(), - gen_event:add_handler(lager_event, lager_common_test_backend, [Level, false]), - %lager:set_loglevel(lager_common_test_backend, Level), + application:set_env(lager, suppress_application_start_stop, true), + application:set_env(lager, handlers, + [ + {lager_common_test_backend, [Level, false]} + ]), + ok = lager:start(), + %% we care more about getting all of our messages here than being + %% careful with the amount of memory that we're using. + error_logger_lager_h:set_high_water(100000), ok. -spec(init(integer()|atom()|[term()]) -> {ok, #state{}} | {error, atom()}).
View file
lager-2.1.0.tar.gz/src/lager_config.erl -> lager-3.1.0.tar.gz/src/lager_config.erl
Changed
@@ -20,9 +20,16 @@ -include("lager.hrl"). --export([new/0, get/1, get/2, set/2]). +-export([new/0, new_sink/1, get/1, get/2, set/2, + global_get/1, global_get/2, global_set/2]). -define(TBL, lager_config). +-define(GLOBAL, '_global'). + +%% For multiple sinks, the key is now the registered event name and the old key +%% as a tuple. +%% +%% {{lager_event, loglevel}, Value} instead of {loglevel, Value} new() -> %% set up the ETS configuration table @@ -33,32 +40,48 @@ error:badarg -> ?INT_LOG(warning, "Table ~p already exists", [?TBL]) end, + new_sink(?DEFAULT_SINK), + %% Need to be able to find the `lager_handler_watcher' for all handlers + ets:insert_new(?TBL, {{?GLOBAL, handlers}, []}), + ok. + +new_sink(Sink) -> %% use insert_new here so that if we're in an appup we don't mess anything up %% %% until lager is completely started, allow all messages to go through - ets:insert_new(?TBL, {loglevel, {element(2, lager_util:config_to_mask(debug)), []}}), - ok. + ets:insert_new(?TBL, {{Sink, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}}). + +global_get(Key) -> + global_get(Key, undefined). + +global_get(Key, Default) -> + get({?GLOBAL, Key}, Default). +global_set(Key, Value) -> + set({?GLOBAL, Key}, Value). + +get({_Sink, _Key}=FullKey) -> + get(FullKey, undefined); get(Key) -> - case ets:lookup(?TBL, Key) of - [] -> - undefined; - [{Key, Res}] -> - Res - end. + get({?DEFAULT_SINK, Key}, undefined). -get(Key, Default) -> - try ?MODULE:get(Key) of - undefined -> +get({Sink, Key}, Default) -> + try + case ets:lookup(?TBL, {Sink, Key}) of + [] -> Default; - Res -> + [{{Sink, Key}, Res}] -> Res + end catch _:_ -> Default - end. + end; +get(Key, Default) -> + get({?DEFAULT_SINK, Key}, Default). +set({Sink, Key}, Value) -> + ets:insert(?TBL, {{Sink, Key}, Value}); set(Key, Value) -> - ets:insert(?TBL, {Key, Value}). - + set({?DEFAULT_SINK, Key}, Value).
View file
lager-2.1.0.tar.gz/src/lager_console_backend.erl -> lager-3.1.0.tar.gz/src/lager_console_backend.erl
Changed
@@ -38,6 +38,8 @@ -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]). %% @private +init([Level]) when is_atom(Level) -> + init(Level); init([Level, true]) -> % for backwards compatibility init([Level,{lager_default_formatter,[{eol, eol()}]}]); init([Level,false]) -> % for backwards compatibility @@ -75,7 +77,6 @@ init(Level) -> init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]). - %% @private handle_call(get_loglevel, #state{level=Level} = State) -> {ok, Level, State}; @@ -186,7 +187,7 @@ register(user, Pid), erlang:group_leader(Pid, whereis(lager_event)), gen_event:add_handler(lager_event, lager_console_backend, info), - lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), + lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:log(info, self(), "Test message"), receive {io_request, From, ReplyAs, {put_chars, unicode, Msg}} -> @@ -206,7 +207,7 @@ register(user, Pid), erlang:group_leader(Pid, whereis(lager_event)), gen_event:add_handler(lager_event, lager_console_backend, [info, true]), - lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), + lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:info("Test message"), PidStr = pid_to_list(self()), receive @@ -228,7 +229,7 @@ gen_event:add_handler(lager_event, lager_console_backend, [info, {lager_default_formatter, [date,"#",time,"#",severity,"#",node,"#",pid,"#", module,"#",function,"#",file,"#",line,"#",message,"\r\n"]}]), - lager_config:set(loglevel, {?INFO, []}), + lager_config:set({lager_event, loglevel}, {?INFO, []}), lager:info("Test message"), PidStr = pid_to_list(self()), NodeStr = atom_to_list(node()), @@ -251,7 +252,7 @@ register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), erlang:group_leader(Pid, whereis(lager_event)), - lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), + lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:debug("Test message"), receive {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} -> @@ -280,7 +281,7 @@ unregister(user), register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), - lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), + lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), erlang:group_leader(Pid, whereis(lager_event)), lager:debug("Test message"), receive @@ -319,7 +320,7 @@ unregister(user), register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), - lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), + lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:set_loglevel(lager_console_backend, '!=info'), erlang:group_leader(Pid, whereis(lager_event)), lager:debug("Test message"), @@ -350,7 +351,7 @@ unregister(user), register(user, Pid), gen_event:add_handler(lager_event, lager_console_backend, info), - lager_config:set(loglevel, {element(2, lager_util:config_to_mask(info)), []}), + lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), lager:set_loglevel(lager_console_backend, '=debug'), erlang:group_leader(Pid, whereis(lager_event)), lager:debug("Test message"),
View file
lager-2.1.0.tar.gz/src/lager_crash_log.erl -> lager-3.1.0.tar.gz/src/lager_crash_log.erl
Changed
@@ -66,7 +66,8 @@ Date, Count], []). %% @private -init([Filename, MaxBytes, Size, Date, Count]) -> +init([RelFilename, MaxBytes, Size, Date, Count]) -> + Filename = lager_util:expand_path(RelFilename), case lager_util:open_logfile(Filename, false) of {ok, {FD, Inode, _}} -> schedule_rotation(Date),
View file
lager-2.1.0.tar.gz/src/lager_default_formatter.erl -> lager-3.1.0.tar.gz/src/lager_default_formatter.erl
Changed
@@ -40,7 +40,7 @@ %% or refer to other properties, if desired. You can also use a {atom, semi-iolist(), semi-iolist()} formatter, which %% acts like a ternary operator's true/false branches. %% -%% The metadata properties date,time, message, and severity will always exist. +%% The metadata properties date,time, message, severity, and sev will always exist. %% The properties pid, file, line, module, and function will always exist if the parser transform is used. %% %% Example: @@ -51,7 +51,7 @@ %% %% `[{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 metada print "My pid is ?.?.?", otherwise print "Unknown Pid" +%% `[{pid, ["My pid is ", pid], ["Unknown Pid"]}]' -> if pid is in the metada print "My pid is ?.?.?", otherwise print "Unknown Pid" %% @end -spec format(lager_msg:lager_msg(),list(),list()) -> any(). format(Msg,[], Colors) -> @@ -86,6 +86,18 @@ T; output(severity,Msg) -> atom_to_list(lager_msg:severity(Msg)); +output(blank,_Msg) -> + output({blank," "},_Msg); +output({blank,Fill},_Msg) -> + Fill; +output(sev,Msg) -> + %% Write brief acronym for the severity level (e.g. debug -> $D) + [lager_util:level_to_chr(lager_msg:severity(Msg))]; +output(metadata, Msg) -> + output({metadata, "=", " "}, Msg); +output({metadata, IntSep, FieldSep}, Msg) -> + MD = lists:keysort(1, lager_msg:metadata(Msg)), + string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep); output(Prop,Msg) when is_atom(Prop) -> Metadata = lager_msg:metadata(Msg), make_printable(get_metadata(Prop,Metadata,<<"Undefined">>)); @@ -101,8 +113,47 @@ _ -> [ output(V, Msg) || V <- Present] end; +output({Prop, Present, Absent, Width}, Msg) when is_atom(Prop) -> + %% sort of like a poor man's ternary operator + Metadata = lager_msg:metadata(Msg), + case get_metadata(Prop, Metadata) of + undefined -> + [ output(V, Msg, Width) || V <- Absent]; + _ -> + [ output(V, Msg, Width) || V <- Present] + end; output(Other,_) -> make_printable(Other). +output(message, Msg, _Width) -> lager_msg:message(Msg); +output(date,Msg, _Width) -> + {D, _T} = lager_msg:datetime(Msg), + D; +output(time, Msg, _Width) -> + {_D, T} = lager_msg:datetime(Msg), + T; +output(severity, Msg, Width) -> + make_printable(atom_to_list(lager_msg:severity(Msg)), Width); +output(sev,Msg, _Width) -> + %% Write brief acronym for the severity level (e.g. debug -> $D) + [lager_util:level_to_chr(lager_msg:severity(Msg))]; +output(blank,_Msg, _Width) -> + output({blank, " "},_Msg, _Width); +output({blank, Fill},_Msg, _Width) -> + Fill; +output(metadata, Msg, _Width) -> + output({metadata, "=", " "}, Msg, _Width); +output({metadata, IntSep, FieldSep}, Msg, _Width) -> + MD = lists:keysort(1, lager_msg:metadata(Msg)), + [string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep)]; + +output(Prop, Msg, Width) when is_atom(Prop) -> + Metadata = lager_msg:metadata(Msg), + make_printable(get_metadata(Prop,Metadata,<<"Undefined">>), Width); +output({Prop,Default},Msg, Width) when is_atom(Prop) -> + Metadata = lager_msg:metadata(Msg), + make_printable(get_metadata(Prop,Metadata,output(Default,Msg)), Width); +output(Other,_, Width) -> make_printable(Other, Width). + output_color(_Msg,[]) -> []; output_color(Msg,Colors) -> Level = lager_msg:severity(Msg), @@ -117,6 +168,21 @@ make_printable(L) when is_list(L) orelse is_binary(L) -> L; make_printable(Other) -> io_lib:format("~p",[Other]). +make_printable(A,W) when is_integer(W)-> string:left(make_printable(A),W); +make_printable(A,{Align,W}) when is_integer(W) -> + case Align of + left -> + string:left(make_printable(A),W); + centre -> + string:centre(make_printable(A),W); + right -> + string:right(make_printable(A),W); + _ -> + string:left(make_printable(A),W) + end; + +make_printable(A,_W) -> make_printable(A). + get_metadata(Key, Metadata) -> get_metadata(Key, Metadata, undefined). @@ -164,7 +230,7 @@ [date, " ", time," [",severity,"] ",pid, " ", message, "\n"] ))) }, - {"Non existant metadata can default to string", + {"Non existent metadata can default to string", ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]), iolist_to_binary(format(lager_msg:new("Message", Now, @@ -174,7 +240,7 @@ [date, " ", time," [",severity,"] ",{does_not_exist,"Fallback"}, " ", message, "\n"] ))) }, - {"Non existant metadata can default to other metadata", + {"Non existent metadata can default to other metadata", ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]), iolist_to_binary(format(lager_msg:new("Message", Now, @@ -183,7 +249,145 @@ []), [date, " ", time," [",severity,"] ",{does_not_exist,pid}, " ", message, "\n"] ))) + }, + {"Non existent metadata can default to a string2", + ?_assertEqual(iolist_to_binary(["Unknown Pid"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [], + []), + [{pid, ["My pid is ", pid], ["Unknown Pid"]}] + ))) + }, + {"Metadata can have extra formatting", + ?_assertEqual(iolist_to_binary(["My pid is hello"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}], + []), + [{pid, ["My pid is ", pid], ["Unknown Pid"]}] + ))) + }, + {"Metadata can have extra formatting1", + ?_assertEqual(iolist_to_binary(["servername"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}, {server, servername}], + []), + [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] + ))) + }, + {"Metadata can have extra formatting2", + ?_assertEqual(iolist_to_binary(["(hello)"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}], + []), + [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] + ))) + }, + {"Metadata can have extra formatting3", + ?_assertEqual(iolist_to_binary(["(Unknown Server)"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [], + []), + [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] + ))) + }, + {"Metadata can be printed in its enterity", + ?_assertEqual(iolist_to_binary(["bar=2 baz=3 foo=1"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{foo, 1}, {bar, 2}, {baz, 3}], + []), + [metadata] + ))) + }, + {"Metadata can be printed in its enterity with custom seperators", + ?_assertEqual(iolist_to_binary(["bar->2, baz->3, foo->1"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{foo, 1}, {bar, 2}, {baz, 3}], + []), + [{metadata, "->", ", "}] + ))) + }, + {"Metadata can have extra formatting with width 1", + ?_assertEqual(iolist_to_binary(["(hello )(hello )(hello)(hello)(hello)"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}], + []), + ["(",{pid, [pid], "", 10},")", + "(",{pid, [pid], "", {bad_align,10}},")", + "(",{pid, [pid], "", bad10},")", + "(",{pid, [pid], "", {right,bad20}},")", + "(",{pid, [pid], "", {bad_align,bad20}},")"] + ))) + }, + {"Metadata can have extra formatting with width 2", + ?_assertEqual(iolist_to_binary(["(hello )"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}], + []), + ["(",{pid, [pid], "", {left,10}},")"] + ))) + }, + {"Metadata can have extra formatting with width 3", + ?_assertEqual(iolist_to_binary(["( hello)"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}], + []), + ["(",{pid, [pid], "", {right,10}},")"] + ))) + }, + {"Metadata can have extra formatting with width 4", + ?_assertEqual(iolist_to_binary(["( hello )"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}], + []), + ["(",{pid, [pid], "", {centre,10}},")"] + ))) + }, + {"Metadata can have extra formatting with width 5", + ?_assertEqual(iolist_to_binary(["error |hello ! ( hello )"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello}], + []), + [{x,"",[severity,{blank,"|"},pid], 10},"!",blank,"(",{pid, [pid], "", {centre,10}},")"] + ))) + }, + {"Metadata can have extra formatting with width 6", + ?_assertEqual(iolist_to_binary([Time,Date," bar=2 baz=3 foo=1 pid=hello EMessage"]), + iolist_to_binary(format(lager_msg:new("Message", + Now, + error, + [{pid, hello},{foo, 1}, {bar, 2}, {baz, 3}], + []), + [{x,"",[time]}, {x,"",[date],20},blank,{x,"",[metadata],30},blank,{x,"",[sev],10},message, {message,message,"", {right,20}}] + ))) } + + + + ]. -endif.
View file
lager-2.1.0.tar.gz/src/lager_file_backend.erl -> lager-3.1.0.tar.gz/src/lager_file_backend.erl
Changed
@@ -63,6 +63,7 @@ size = 0 :: integer(), date :: undefined | string(), count = 10 :: integer(), + shaper :: lager_shaper(), formatter :: atom(), formatter_config :: any(), sync_on :: {'mask', integer()}, @@ -74,26 +75,27 @@ -type option() :: {file, string()} | {level, lager:log_level()} | {size, non_neg_integer()} | {date, string()} | - {count, non_neg_integer()} | {sync_interval, non_neg_integer()} | + {count, non_neg_integer()} | {high_water_mark, non_neg_integer()} | + {sync_interval, non_neg_integer()} | {sync_size, non_neg_integer()} | {sync_on, lager:log_level()} | {check_interval, non_neg_integer()} | {formatter, atom()} | {formatter_config, term()}. -spec init([option(),...]) -> {ok, #state{}} | {error, bad_config}. init({FileName, LogLevel}) when is_list(FileName), is_atom(LogLevel) -> - %% backwards compatability hack + %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}]); init({FileName, LogLevel, Size, Date, Count}) when is_list(FileName), is_atom(LogLevel) -> - %% backwards compatability hack + %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}]); init([{FileName, LogLevel, Size, Date, Count}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) -> - %% backwards compatability hack + %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}, {size, Size}, {date, Date}, {count, Count}, {formatter, Formatter}, {formatter_config, FormatterConfig}]); init([LogFile,{Formatter}]) -> - %% backwards compatability hack + %% backwards compatibility hack init([LogFile,{Formatter,[]}]); init([{FileName, LogLevel}, {Formatter,FormatterConfig}]) when is_list(FileName), is_atom(LogLevel), is_atom(Formatter) -> - %% backwards compatability hack + %% backwards compatibility hack init([{file, FileName}, {level, LogLevel}, {formatter, Formatter}, {formatter_config, FormatterConfig}]); init(LogFileConfig) when is_list(LogFileConfig) -> case validate_logfile_proplist(LogFileConfig) of @@ -102,10 +104,12 @@ {error, {fatal, bad_config}}; Config -> %% probabably a better way to do this, but whatever - [Name, Level, Date, Size, Count, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] = - [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]], + [RelName, Level, Date, Size, Count, HighWaterMark, SyncInterval, SyncSize, SyncOn, CheckInterval, Formatter, FormatterConfig] = + [proplists:get_value(Key, Config) || Key <- [file, level, date, size, count, high_water_mark, sync_interval, sync_size, sync_on, check_interval, formatter, formatter_config]], + Name = lager_util:expand_path(RelName), schedule_rotation(Name, Date), - State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, formatter=Formatter, + Shaper = #lager_shaper{hwm=HighWaterMark}, + State0 = #state{name=Name, level=Level, size=Size, date=Date, count=Count, shaper=Shaper, formatter=Formatter, formatter_config=FormatterConfig, sync_on=SyncOn, sync_interval=SyncInterval, sync_size=SyncSize, check_interval=CheckInterval}, State = case lager_util:open_logfile(Name, {SyncSize, SyncInterval}) of @@ -129,15 +133,45 @@ end; handle_call(get_loglevel, #state{level=Level} = State) -> {ok, Level, State}; +handle_call({set_loghwm, Hwm}, #state{shaper=Shaper, name=Name} = State) -> + case validate_logfile_proplist([{file, Name}, {high_water_mark, Hwm}]) of + false -> + {ok, {error, bad_log_hwm}, State}; + _ -> + NewShaper = Shaper#lager_shaper{hwm=Hwm}, + ?INT_LOG(notice, "Changed loghwm of ~s to ~p", [Name, Hwm]), + {ok, {last_loghwm, Shaper#lager_shaper.hwm}, State#state{shaper=NewShaper}} + end; +handle_call(rotate, State = #state{name=File}) -> + {ok, NewState} = handle_info({rotate, File}, State), + {ok, ok, NewState}; handle_call(_Request, State) -> {ok, ok, State}. %% @private handle_event({log, Message}, - #state{name=Name, level=L,formatter=Formatter,formatter_config=FormatConfig} = State) -> + #state{name=Name, level=L, shaper=Shaper, formatter=Formatter,formatter_config=FormatConfig} = State) -> case lager_util:is_loggable(Message,L,{lager_file_backend, Name}) of true -> - {ok,write(State, lager_msg:timestamp(Message), lager_msg:severity_as_int(Message), Formatter:format(Message,FormatConfig)) }; + case lager_util:check_hwm(Shaper) of + {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} -> + NewState = case Drop > 0 of + true -> + Report = io_lib:format( + "lager_file_backend dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", + [Drop, Hwm]), + ReportMsg = lager_msg:new(Report, warning, [], []), + write(State, lager_msg:timestamp(ReportMsg), + lager_msg:severity_as_int(ReportMsg), Formatter:format(ReportMsg, FormatConfig)); + false -> + State + end, + {ok,write(NewState#state{shaper=NewShaper}, + lager_msg:timestamp(Message), lager_msg:severity_as_int(Message), + Formatter:format(Message,FormatConfig))}; + {false, _, NewShaper} -> + {ok, State#state{shaper=NewShaper}} + end; false -> {ok, State} end; @@ -147,23 +181,23 @@ %% @private handle_info({rotate, File}, #state{name=File,count=Count,date=Date} = State) -> _ = lager_util:rotate_logfile(File, Count), + State1 = close_file(State), schedule_rotation(File, Date), - {ok, State}; + {ok, State1}; handle_info(_Info, State) -> {ok, State}. %% @private -terminate(_Reason, #state{fd=FD}) -> - %% flush and close any file handles - _ = file:datasync(FD), - _ = file:close(FD), +terminate(_Reason, State) -> + %% leaving this function call unmatched makes dialyzer cranky + _ = close_file(State), ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. -%% @private convert the config into a gen_event handler ID +%% Convert the config into a gen_event handler ID config_to_id({Name,_Severity}) when is_list(Name) -> {?MODULE, Name}; config_to_id({Name,_Severity,_Size,_Rotation,_Count}) -> @@ -236,7 +270,7 @@ Flap end, State#state{flap=Flap2}; - _ -> + _ -> State end. @@ -300,6 +334,13 @@ _ -> throw({bad_config, "Invalid rotation count", Count}) end; +validate_logfile_proplist([{high_water_mark, HighWaterMark}|Tail], Acc) -> + case HighWaterMark of + Hwm when is_integer(Hwm), Hwm >= 0 -> + validate_logfile_proplist(Tail, [{high_water_mark, Hwm}|Acc]); + _ -> + throw({bad_config, "Invalid high water mark", HighWaterMark}) + end; validate_logfile_proplist([{date, Date}|Tail], Acc) -> case lager_util:parse_rotation_date_spec(Date) of {ok, Spec} -> @@ -363,6 +404,14 @@ erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), {rotate, Name}), ok. +close_file(#state{fd=undefined} = State) -> + State; +close_file(#state{fd=FD} = State) -> + %% Flush and close any file handles. + _ = file:datasync(FD), + _ = file:close(FD), + State#state{fd=undefined}. + -ifdef(TEST). get_loglevel_test() -> @@ -591,6 +640,16 @@ ?assert(filelib:is_regular("test.log.0")) end }, + {"rotation call should work", + fun() -> + gen_event:add_handler(lager_event, {lager_file_backend, "test.log"}, [{file, "test.log"}, {level, info}, {check_interval, 1000}]), + lager:log(error, self(), "Test message1"), + lager:log(error, self(), "Test message1"), + gen_event:call(lager_event, {lager_file_backend, "test.log"}, rotate, infinity), + lager:log(error, self(), "Test message1"), + ?assert(filelib:is_regular("test.log.0")) + end + }, {"sync_on option should work", fun() -> gen_event:add_handler(lager_event, lager_file_backend, [{file, "test.log"}, {level, info}, {sync_on, "=info"}, {check_interval, 5000}, {sync_interval, 5000}]), @@ -665,8 +724,8 @@ {"test.log", critical}), lager:error("Test message"), ?assertEqual({ok, <<>>}, file:read_file("test.log")), - {Level, _} = lager_config:get(loglevel), - lager_config:set(loglevel, {Level, [{[{module, + {Level, _} = lager_config:get({lager_event, loglevel}), + lager_config:set({lager_event, loglevel}, {Level, [{[{module, ?MODULE}], ?DEBUG, {lager_file_backend, "test.log"}}]}), lager:error("Test message"), @@ -683,8 +742,8 @@ {ok, Bin1} = file:read_file("test.log"), ?assertMatch([_, _, "[critical]", _, "Test message\n"], re:split(Bin1, " ", [{return, list}, {parts, 5}])), ok = file:delete("test.log"), - {Level, _} = lager_config:get(loglevel), - lager_config:set(loglevel, {Level, [{[{module, + {Level, _} = lager_config:get({lager_event, loglevel}), + lager_config:set({lager_event, loglevel}, {Level, [{[{module, ?MODULE}], ?DEBUG, {lager_file_backend, "test.log"}}]}), lager:critical("Test message"), @@ -707,12 +766,31 @@ ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}])) end }, + {"tracing to a dedicated file should work even if root_log is set", + fun() -> + {ok, P} = file:get_cwd(), + file:delete(P ++ "/test_root_log/foo.log"), + application:set_env(lager, log_root, P++"/test_root_log"), + {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}]), + lager:error("Test message"), + %% not elegible for trace + lager:log(error, self(), "Test message"), + {ok, Bin3} = file:read_file(P++"/test_root_log/foo.log"), + application:unset_env(lager, log_root), + ?assertMatch([_, _, "[error]", _, "Test message\n"], re:split(Bin3, " ", [{return, list}, {parts, 5}])) + end + }, {"tracing with options should work", fun() -> file:delete("foo.log"), - {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}], [{size, 20}, {check_interval, 1}]), + {ok, _} = lager:trace_file("foo.log", [{module, ?MODULE}], [{size, 20}, {check_interval, 1}]), lager:error("Test message"), ?assertNot(filelib:is_regular("foo.log.0")), + %% rotation is sensitive to intervals between + %% writes so we sleep to exceed the 1 + %% millisecond interval specified by + %% check_interval above + timer:sleep(2), lager:error("Test message"), timer:sleep(10), ?assert(filelib:is_regular("foo.log.0")) @@ -768,6 +846,10 @@ ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {count, infinity}])) }, + {"bad high water mark", + ?_assertEqual(false, + validate_logfile_proplist([{file, "test.log"}, {high_water_mark, infinity}])) + }, {"bad date", ?_assertEqual(false, validate_logfile_proplist([{file, "test.log"}, {date, "midnight"}])) @@ -808,4 +890,3 @@ -endif. -
View file
lager-2.1.0.tar.gz/src/lager_handler_watcher.erl -> lager-3.1.0.tar.gz/src/lager_handler_watcher.erl
Changed
@@ -38,18 +38,18 @@ -record(state, { module :: atom(), config :: any(), - event :: pid() | atom() + sink :: pid() | atom() }). -start_link(Event, Module, Config) -> - gen_server:start_link(?MODULE, [Event, Module, Config], []). +start_link(Sink, Module, Config) -> + gen_server:start_link(?MODULE, [Sink, Module, Config], []). -start(Event, Module, Config) -> - gen_server:start(?MODULE, [Event, Module, Config], []). +start(Sink, Module, Config) -> + gen_server:start(?MODULE, [Sink, Module, Config], []). -init([Event, Module, Config]) -> - install_handler(Event, Module, Config), - {ok, #state{event=Event, module=Module, config=Config}}. +init([Sink, Module, Config]) -> + install_handler(Sink, Module, Config), + {ok, #state{sink=Sink, module=Module, config=Config}}. handle_call(_Call, _From, State) -> {reply, ok, State}. @@ -62,18 +62,18 @@ handle_info({gen_event_EXIT, Module, shutdown}, #state{module=Module} = State) -> {stop, normal, State}; handle_info({gen_event_EXIT, Module, Reason}, #state{module=Module, - config=Config, event=Event} = State) -> + config=Config, sink=Sink} = State) -> case lager:log(error, self(), "Lager event handler ~p exited with reason ~s", [Module, error_logger_lager_h:format_reason(Reason)]) of ok -> - install_handler(Event, Module, Config); + install_handler(Sink, Module, Config); {error, _} -> %% lager is not working, so installing a handler won't work ok end, {noreply, State}; -handle_info(reinstall_handler, #state{module=Module, config=Config, event=Event} = State) -> - install_handler(Event, Module, Config), +handle_info(reinstall_handler, #state{module=Module, config=Config, sink=Sink} = State) -> + install_handler(Sink, Module, Config), {noreply, State}; handle_info(stop, State) -> {stop, normal, State}; @@ -87,23 +87,33 @@ {ok, State}. %% internal - -install_handler(Event, Module, Config) -> - case gen_event:add_sup_handler(Event, Module, Config) of +install_handler(Sink, lager_backend_throttle, Config) -> + %% The lager_backend_throttle needs to know to which sink it is + %% attached, hence this admittedly ugly workaround. Handlers are + %% sensitive to the structure of the configuration sent to `init', + %% sadly, so it's not trivial to add a configuration item to be + %% ignored to backends without breaking 3rd party handlers. + install_handler2(Sink, lager_backend_throttle, [{sink, Sink}|Config]); +install_handler(Sink, Module, Config) -> + install_handler2(Sink, Module, Config). + +%% private +install_handler2(Sink, Module, Config) -> + case gen_event:add_sup_handler(Sink, Module, Config) of ok -> - ?INT_LOG(debug, "Lager installed handler ~p into ~p", [Module, Event]), - lager:update_loglevel_config(), + ?INT_LOG(debug, "Lager installed handler ~p into ~p", [Module, Sink]), + lager:update_loglevel_config(Sink), ok; {error, {fatal, Reason}} -> ?INT_LOG(error, "Lager fatally failed to install handler ~p into" - " ~p, NOT retrying: ~p", [Module, Event, Reason]), + " ~p, NOT retrying: ~p", [Module, Sink, Reason]), %% tell ourselves to stop self() ! stop, ok; Error -> %% try to reinstall it later ?INT_LOG(error, "Lager failed to install handler ~p into" - " ~p, retrying later : ~p", [Module, Event, Error]), + " ~p, retrying later : ~p", [Module, Sink, Error]), erlang:send_after(5000, self(), reinstall_handler), ok end.
View file
lager-2.1.0.tar.gz/src/lager_stdlib.erl -> lager-3.1.0.tar.gz/src/lager_stdlib.erl
Changed
@@ -39,7 +39,7 @@ string_p(Term) -> string_p1(Term). -string_p1([H|T]) when is_integer(H), H >= $\s, H < 255 -> +string_p1([H|T]) when is_integer(H), H >= $\s, H < 256 -> string_p1(T); string_p1([$\n|T]) -> string_p1(T); string_p1([$\r|T]) -> string_p1(T);
View file
lager-2.1.0.tar.gz/src/lager_sup.erl -> lager-3.1.0.tar.gz/src/lager_sup.erl
Changed
@@ -34,9 +34,14 @@ init([]) -> %% set up the config, is safe even during relups lager_config:new(), + %% TODO: + %% Always start lager_event as the default and make sure that + %% other gen_event stuff can start up as needed + %% + %% Maybe a new API to handle the sink and its policy? Children = [ {lager, {gen_event, start_link, [{local, lager_event}]}, - permanent, 5000, worker, [dynamic]}, + permanent, 5000, worker, dynamic}, {lager_handler_watcher_sup, {lager_handler_watcher_sup, start_link, []}, permanent, 5000, supervisor, [lager_handler_watcher_sup]}],
View file
lager-2.1.0.tar.gz/src/lager_transform.erl -> lager-3.1.0.tar.gz/src/lager_transform.erl
Changed
@@ -31,8 +31,10 @@ parse_transform(AST, Options) -> TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION), Enable = proplists:get_value(lager_print_records_flag, Options, true), + Sinks = [lager] ++ proplists:get_value(lager_extra_sinks, Options, []), put(print_records_flag, Enable), put(truncation_size, TruncSize), + put(sinks, Sinks), erlang:put(records, []), %% .app file should either be in the outdir, or the same dir as the source file guess_application(proplists:get_value(outdir, Options), hd(AST)), @@ -75,138 +77,163 @@ walk_body(Acc, []) -> lists:reverse(Acc); walk_body(Acc, [H|T]) -> - walk_body([transform_statement(H)|Acc], T). + walk_body([transform_statement(H, get(sinks))|Acc], T). -transform_statement({call, Line, {remote, _Line1, {atom, _Line2, lager}, - {atom, _Line3, Severity}}, Arguments0} = Stmt) -> - case lists:member(Severity, ?LEVELS) of +transform_statement({call, Line, {remote, _Line1, {atom, _Line2, Module}, + {atom, _Line3, Function}}, Arguments0} = Stmt, + Sinks) -> + case lists:member(Module, Sinks) of true -> - SeverityAsInt=lager_util:level_to_num(Severity), - DefaultAttrs0 = {cons, Line, {tuple, Line, [ - {atom, Line, module}, {atom, Line, get(module)}]}, - {cons, Line, {tuple, Line, [ - {atom, Line, function}, {atom, Line, get(function)}]}, - {cons, Line, {tuple, Line, [ - {atom, Line, line}, - {integer, Line, Line}]}, - {cons, Line, {tuple, Line, [ - {atom, Line, pid}, - {call, Line, {atom, Line, pid_to_list}, [ - {call, Line, {atom, Line ,self}, []}]}]}, - {cons, Line, {tuple, Line, [ - {atom, Line, node}, - {call, Line, {atom, Line, node}, []}]}, - %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here - {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}}, - %{nil, Line}}}}}}}, - DefaultAttrs = case erlang:get(application) of - undefined -> - DefaultAttrs0; - App -> - %% stick the application in the attribute list - concat_lists({cons, Line, {tuple, Line, [ - {atom, Line, application}, - {atom, Line, App}]}, - {nil, Line}}, DefaultAttrs0) - end, - {Traces, Message, Arguments} = case Arguments0 of - [Format] -> - {DefaultAttrs, Format, {atom, Line, none}}; - [Arg1, Arg2] -> - %% some ambiguity here, figure out if these arguments are - %% [Format, Args] or [Attr, Format]. - %% The trace attributes will be a list of tuples, so check - %% for that. - case {element(1, Arg1), Arg1} of - {_, {cons, _, {tuple, _, _}, _}} -> - {concat_lists(Arg1, DefaultAttrs), - Arg2, {atom, Line, none}}; - {Type, _} when Type == var; - Type == lc; - Type == call; - Type == record_field -> - %% crap, its not a literal. look at the second - %% argument to see if it is a string - case Arg2 of - {string, _, _} -> - {concat_lists(Arg1, DefaultAttrs), - Arg2, {atom, Line, none}}; - _ -> - %% not a string, going to have to guess - %% it's the argument list - {DefaultAttrs, Arg1, Arg2} - end; - _ -> - {DefaultAttrs, Arg1, Arg2} - end; - [Attrs, Format, Args] -> - {concat_lists(Attrs, DefaultAttrs), Format, Args} - end, - %% Generate some unique variable names so we don't accidentaly export from case clauses. - %% Note that these are not actual atoms, but the AST treats variable names as atoms. - LevelVar = make_varname("__Level", Line), - TracesVar = make_varname("__Traces", Line), - PidVar = make_varname("__Pid", Line), - %% Wrap the call to lager_dispatch log in a case that will avoid doing any work if this message is not elegible for logging - %% case {whereis(lager_event(lager_event), lager_config:get(loglevel, {?LOG_NONE, []})} of - {'case', Line, - {tuple, Line, - [{call, Line, {atom, Line, whereis}, [{atom, Line, lager_event}]}, - {call, Line, {remote, Line, {atom, Line, lager_config}, {atom, Line, get}}, [{atom, Line, loglevel}, {tuple, Line, [{integer, Line, 0},{nil, Line}]}]}]}, - [ - %% {undefined, _} -> {error, lager_not_running} - {clause, Line, - [{tuple, Line, [{atom, Line, undefined}, {var, Line, '_'}]}], - [], - %% trick the linter into avoiding a 'term constructed by not used' error: - %% (fun() -> {error, lager_not_running} end)(); - [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}]}, - %% If we care about the loglevel, or there's any traces installed, we have do more checking - %% {Level, Traces} when (Level band SeverityAsInt) /= 0 orelse Traces /= [] -> - {clause, Line, - [{tuple, Line, [{var, Line, PidVar}, {tuple, Line, [{var, Line, LevelVar}, {var, Line, TracesVar}]}]}], - [[{op, Line, 'orelse', - {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}}, - {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]], - [ - %% do the call to lager:dispatch_log - {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, do_log}}, - [ - {atom,Line,Severity}, - Traces, - Message, - Arguments, - {integer, Line, get(truncation_size)}, - {integer, Line, SeverityAsInt}, - {var, Line, LevelVar}, - {var, Line, TracesVar}, - {var, Line, PidVar} - ] - } - ]}, - %% otherwise, do nothing - %% _ -> ok - {clause, Line, [{var, Line, '_'}],[],[{atom, Line, ok}]} - ]}; + case lists:member(Function, ?LEVELS) of + true -> + SinkName = lager_util:make_internal_sink_name(Module), + do_transform(Line, SinkName, Function, Arguments0); + false -> + case lists:keyfind(Function, 1, ?LEVELS_UNSAFE) of + {Function, Severity} -> + SinkName = lager_util:make_internal_sink_name(Module), + do_transform(Line, SinkName, Severity, Arguments0, unsafe); + false -> + Stmt + end + end; false -> - Stmt + list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)) end; -transform_statement({call, Line, {remote, Line1, {atom, Line2, boston_lager}, - {atom, Line3, Severity}}, Arguments}) -> - NewArgs = case Arguments of - [{string, L, Msg}] -> [{string, L, re:replace(Msg, "r", "h", [{return, list}, global])}]; - [{string, L, Format}, Args] -> [{string, L, re:replace(Format, "r", "h", [{return, list}, global])}, Args]; - Other -> Other - end, - transform_statement({call, Line, {remote, Line1, {atom, Line2, lager}, - {atom, Line3, Severity}}, NewArgs}); -transform_statement(Stmt) when is_tuple(Stmt) -> - list_to_tuple(transform_statement(tuple_to_list(Stmt))); -transform_statement(Stmt) when is_list(Stmt) -> - [transform_statement(S) || S <- Stmt]; -transform_statement(Stmt) -> +transform_statement(Stmt, Sinks) when is_tuple(Stmt) -> + list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)); +transform_statement(Stmt, Sinks) when is_list(Stmt) -> + [transform_statement(S, Sinks) || S <- Stmt]; +transform_statement(Stmt, _Sinks) -> Stmt. +do_transform(Line, SinkName, Severity, Arguments0) -> + do_transform(Line, SinkName, Severity, Arguments0, safe). + +do_transform(Line, SinkName, Severity, Arguments0, Safety) -> + SeverityAsInt=lager_util:level_to_num(Severity), + DefaultAttrs0 = {cons, Line, {tuple, Line, [ + {atom, Line, module}, {atom, Line, get(module)}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, function}, {atom, Line, get(function)}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, line}, + {integer, Line, Line}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, pid}, + {call, Line, {atom, Line, pid_to_list}, [ + {call, Line, {atom, Line ,self}, []}]}]}, + {cons, Line, {tuple, Line, [ + {atom, Line, node}, + {call, Line, {atom, Line, node}, []}]}, + %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here + {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}}, + %{nil, Line}}}}}}}, + DefaultAttrs = case erlang:get(application) of + undefined -> + DefaultAttrs0; + App -> + %% stick the application in the attribute list + concat_lists({cons, Line, {tuple, Line, [ + {atom, Line, application}, + {atom, Line, App}]}, + {nil, Line}}, DefaultAttrs0) + end, + {Meta, Message, Arguments} = case Arguments0 of + [Format] -> + {DefaultAttrs, Format, {atom, Line, none}}; + [Arg1, Arg2] -> + %% some ambiguity here, figure out if these arguments are + %% [Format, Args] or [Attr, Format]. + %% The trace attributes will be a list of tuples, so check + %% for that. + case {element(1, Arg1), Arg1} of + {_, {cons, _, {tuple, _, _}, _}} -> + {concat_lists(Arg1, DefaultAttrs), + Arg2, {atom, Line, none}}; + {Type, _} when Type == var; + Type == lc; + Type == call; + Type == record_field -> + %% crap, its not a literal. look at the second + %% argument to see if it is a string + case Arg2 of + {string, _, _} -> + {concat_lists(Arg1, DefaultAttrs), + Arg2, {atom, Line, none}}; + _ -> + %% not a string, going to have to guess + %% it's the argument list + {DefaultAttrs, Arg1, Arg2} + end; + _ -> + {DefaultAttrs, Arg1, Arg2} + end; + [Attrs, Format, Args] -> + {concat_lists(Attrs, DefaultAttrs), Format, Args} + end, + %% Generate some unique variable names so we don't accidentally export from case clauses. + %% Note that these are not actual atoms, but the AST treats variable names as atoms. + LevelVar = make_varname("__Level", Line), + TracesVar = make_varname("__Traces", Line), + PidVar = make_varname("__Pid", Line), + LogFun = case Safety of + safe -> + do_log; + unsafe -> + do_log_unsafe + end, + %% Wrap the call to lager:dispatch_log/6 in case that will avoid doing any work if this message is not elegible for logging + %% See lager.erl (lines 89-100) for lager:dispatch_log/6 + %% case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of + {'case',Line, + {tuple,Line, + [{call,Line,{atom,Line,whereis},[{atom,Line,SinkName}]}, + {call,Line,{atom,Line,whereis},[{atom,Line,?DEFAULT_SINK}]}, + {call,Line, + {remote,Line,{atom,Line,lager_config},{atom,Line,get}}, + [{tuple,Line,[{atom,Line,SinkName},{atom,Line,loglevel}]}, + {tuple,Line,[{integer,Line,0},{nil,Line}]}]}]}, + %% {undefined, undefined, _} -> {error, lager_not_running}; + [{clause,Line, + [{tuple,Line, + [{atom,Line,undefined},{atom,Line,undefined},{var,Line,'_'}]}], + [], + %% trick the linter into avoiding a 'term constructed but not used' error: + %% (fun() -> {error, lager_not_running} end)() + [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}] + }, + %% {undefined, _, _} -> {error, {sink_not_configured, Sink}}; + {clause,Line, + [{tuple,Line, + [{atom,Line,undefined},{var,Line,'_'},{var,Line,'_'}]}], + [], + %% same trick as above to avoid linter error + [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple,Line, [{atom,Line,error}, {tuple,Line,[{atom,Line,sink_not_configured},{atom,Line,SinkName}]}]}]}]}}, []}] + }, + %% {SinkPid, _, {Level, Traces}} when ... -> lager:do_log/9; + {clause,Line, + [{tuple,Line, + [{var,Line,PidVar}, + {var,Line,'_'}, + {tuple,Line,[{var,Line,LevelVar},{var,Line,TracesVar}]}]}], + [[{op, Line, 'orelse', + {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}}, + {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]], + [{call,Line,{remote, Line, {atom, Line, lager}, {atom, Line, LogFun}}, + [{atom,Line,Severity}, + Meta, + Message, + Arguments, + {integer, Line, get(truncation_size)}, + {integer, Line, SeverityAsInt}, + {var, Line, LevelVar}, + {var, Line, TracesVar}, + {atom, Line, SinkName}, + {var, Line, PidVar}]}]}, + %% _ -> ok + {clause,Line,[{var,Line,'_'}],[],[{atom,Line,ok}]}]}. + make_varname(Prefix, Line) -> list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)).
View file
lager-2.1.0.tar.gz/src/lager_trunc_io.erl -> lager-3.1.0.tar.gz/src/lager_trunc_io.erl
Changed
@@ -3,12 +3,12 @@ %% compliance with the License. You should have received a copy of the %% Erlang Public License along with your Erlang distribution. If not, it can be %% retrieved via the world wide web at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% The Initial Developer of the Original Code is Corelatus AB. %% Portions created by Corelatus are Copyright 2003, Corelatus %% AB. All Rights Reserved.'' @@ -32,7 +32,7 @@ -module(lager_trunc_io). -author('matthias@corelatus.se'). -%% And thanks to Chris Newcombe for a bug fix +%% And thanks to Chris Newcombe for a bug fix -export([format/3, format/4, print/2, print/3, fprint/2, fprint/3, safe/2]). % interface functions -version("$Id: trunc_io.erl,v 1.11 2009-02-23 12:01:06 matthias Exp $"). @@ -76,11 +76,11 @@ %% @doc Returns an flattened list containing the ASCII representation of the given %% term. -spec fprint(term(), pos_integer(), options()) -> string(). -fprint(T, Max, Options) -> +fprint(T, Max, Options) -> {L, _} = print(T, Max, prepare_options(Options, #print_options{})), lists:flatten(L). -%% @doc Same as print, but never crashes. +%% @doc Same as print, but never crashes. %% %% This is a tradeoff. Print might conceivably crash if it's asked to %% print something it doesn't understand, for example some new data @@ -88,7 +88,7 @@ %% to io_lib to format the term, but then the formatting is %% depth-limited instead of length limited, so you might run out %% memory printing it. Out of the frying pan and into the fire. -%% +%% -spec safe(term(), pos_integer()) -> {string(), pos_integer()} | {string()}. safe(What, Len) -> case catch print(What, Len) of @@ -114,8 +114,8 @@ print(_, _, #print_options{depth=0}) -> {"...", 3}; -%% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need -%% to be truncated. This isn't strictly true, someone could make an +%% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need +%% to be truncated. This isn't strictly true, someone could make an %% arbitrarily long bignum. Let's assume that won't happen unless someone %% is being malicious. %% @@ -214,15 +214,15 @@ SizeStr = integer_to_list(Size), {[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) +1}; print(BitString, Max, Options) when is_bitstring(BitString) -> - case byte_size(BitString) > Max of + BL = case byte_size(BitString) > Max of true -> - BL = binary_to_list(BitString, 1, Max); + binary_to_list(BitString, 1, Max); _ -> R = erlang:bitstring_to_list(BitString), {Bytes, [Bits]} = lists:splitwith(fun erlang:is_integer/1, R), %% tag the trailing bits with a special tuple we catch when %% list_body calls print again - BL = Bytes ++ [{inline_bitstring, Bits}] + Bytes ++ [{inline_bitstring, Bits}] end, {X, Len0} = list_body(BL, Max - 4, dec_depth(Options), true), {["<<", X, ">>"], Len0 + 4}; @@ -265,7 +265,7 @@ {RC, Len} = record_fields(Fields, Max - length(Leader) + 1, dec_depth(Options)), {[Leader, RC, "}"], Len + length(Leader) + 1}; -print(Tuple, Max, Options) when is_tuple(Tuple) -> +print(Tuple, Max, Options) when is_tuple(Tuple) -> {TC, Len} = tuple_contents(Tuple, Max-2, Options), {[${, TC, $}], Len + 2}; @@ -307,7 +307,7 @@ false -> $| end, {[List ++ [Sep | "..."]], Len + 4}; -list_body([H|T], Max, Options, Tuple) -> +list_body([H|T], Max, Options, Tuple) -> {List, Len} = print(H, Max, Options), {Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple), {[List|Final], FLen + Len}; @@ -319,7 +319,7 @@ list_bodyc(_, Max, _Options, _Tuple) when Max < 5 -> {",...", 4}; list_bodyc(_, _Max, #print_options{depth=1}, true) -> {",...", 4}; list_bodyc(_, _Max, #print_options{depth=1}, false) -> {"|...", 4}; -list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) -> +list_bodyc([H|T], Max, #print_options{depth=Depth} = Options, Tuple) -> {List, Len} = print(H, Max, dec_depth(Options)), {Final, FLen} = list_bodyc(T, Max - Len - 1, dec_depth(Options), Tuple), Sep = case Depth == 1 andalso not Tuple of @@ -553,7 +553,7 @@ test(M,F), perf(M,F,Reps-1); perf(_,_,_) -> - done. + done. %% Performance test. Needs a particularly large term I saved as a binary... -spec perf1() -> {non_neg_integer(), non_neg_integer()}. @@ -570,7 +570,7 @@ ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~p", [["foo", $b, $a, $r]], 50))), ?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~P", [["foo", $b, $a, $r], 10], 50))), ?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~w", [["foo", $b, $a, $r]], 50))), - + %% complex ones ?assertEqual(" foobar", lists:flatten(format("~10s", [["foo", $b, $a, $r]], 50))), ?assertEqual("f", lists:flatten(format("~1s", [["foo", $b, $a, $r]], 50))), @@ -836,7 +836,7 @@ ?assertEqual("[1|...]", lists:flatten(format("~P", [[1, 2, 3], 2], 50))), ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, 3], 3], 50))), ?assertEqual("[1,2,3]", lists:flatten(format("~P", [[1, 2, 3], 4], 50))), - + ?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))), ?assertEqual("{1,2,...}", lists:flatten(format("~P", [{1, 2, 3}, 3], 50))), ?assertEqual("{1,2,3}", lists:flatten(format("~P", [{1, 2, 3}, 4], 50))), @@ -845,13 +845,13 @@ ?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, <<3>>], 3], 50))), ?assertEqual("[1,2,<<...>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 4], 50))), ?assertEqual("[1,2,<<3>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 5], 50))), - + ?assertEqual("<<...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 1], 50))), ?assertEqual("<<0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 2], 50))), ?assertEqual("<<0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 3], 50))), ?assertEqual("<<0,0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 4], 50))), ?assertEqual("<<0,0,0,0>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 5], 50))), - + %% this is a seriously weird edge case ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 2], 50))), ?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 3], 50))),
View file
lager-2.1.0.tar.gz/src/lager_util.erl -> lager-3.1.0.tar.gz/src/lager_util.erl
Changed
@@ -18,11 +18,12 @@ -include_lib("kernel/include/file.hrl"). --export([levels/0, level_to_num/1, num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1, +-export([levels/0, level_to_num/1, level_to_chr/1, + num_to_level/1, config_to_mask/1, config_to_levels/1, mask_to_levels/1, open_logfile/2, ensure_logfile/4, rotate_logfile/2, format_time/0, format_time/1, localtime_ms/0, localtime_ms/1, maybe_utc/1, parse_rotation_date_spec/1, calculate_next_rotation/1, validate_trace/1, check_traces/4, is_loggable/3, - trace_filter/1, trace_filter/2]). + trace_filter/1, trace_filter/2, expand_path/1, check_hwm/1, make_internal_sink_name/1]). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). @@ -33,25 +34,35 @@ levels() -> [debug, info, notice, warning, error, critical, alert, emergency, none]. -level_to_num(debug) -> ?DEBUG; -level_to_num(info) -> ?INFO; -level_to_num(notice) -> ?NOTICE; -level_to_num(warning) -> ?WARNING; -level_to_num(error) -> ?ERROR; -level_to_num(critical) -> ?CRITICAL; -level_to_num(alert) -> ?ALERT; -level_to_num(emergency) -> ?EMERGENCY; -level_to_num(none) -> ?LOG_NONE. - -num_to_level(?DEBUG) -> debug; -num_to_level(?INFO) -> info; -num_to_level(?NOTICE) -> notice; -num_to_level(?WARNING) -> warning; -num_to_level(?ERROR) -> error; -num_to_level(?CRITICAL) -> critical; -num_to_level(?ALERT) -> alert; +level_to_num(debug) -> ?DEBUG; +level_to_num(info) -> ?INFO; +level_to_num(notice) -> ?NOTICE; +level_to_num(warning) -> ?WARNING; +level_to_num(error) -> ?ERROR; +level_to_num(critical) -> ?CRITICAL; +level_to_num(alert) -> ?ALERT; +level_to_num(emergency) -> ?EMERGENCY; +level_to_num(none) -> ?LOG_NONE. + +level_to_chr(debug) -> $D; +level_to_chr(info) -> $I; +level_to_chr(notice) -> $N; +level_to_chr(warning) -> $W; +level_to_chr(error) -> $E; +level_to_chr(critical) -> $C; +level_to_chr(alert) -> $A; +level_to_chr(emergency) -> $M; +level_to_chr(none) -> $ . + +num_to_level(?DEBUG) -> debug; +num_to_level(?INFO) -> info; +num_to_level(?NOTICE) -> notice; +num_to_level(?WARNING) -> warning; +num_to_level(?ERROR) -> error; +num_to_level(?CRITICAL) -> critical; +num_to_level(?ALERT) -> alert; num_to_level(?EMERGENCY) -> emergency; -num_to_level(?LOG_NONE) -> none. +num_to_level(?LOG_NONE) -> none. -spec config_to_mask(atom()|string()) -> {'mask', integer()}. config_to_mask(Conf) -> @@ -467,6 +478,67 @@ i3l(I) when I < 100 -> [$0 | i2l(I)]; i3l(I) -> integer_to_list(I). +%% When log_root option is provided, get the real path to a file +expand_path(RelPath) -> + case application:get_env(lager, log_root) of + {ok, LogRoot} when is_list(LogRoot) -> % Join relative path + %% check if the given RelPath contains LogRoot, if so, do not add + %% it again; see gh #304 + case string:str(filename:dirname(RelPath), LogRoot) of + X when X > 0 -> + RelPath; + _Zero -> + filename:join(LogRoot, RelPath) + end; + undefined -> % No log_root given, keep relative path + RelPath + end. + +%% Log rate limit, i.e. high water mark for incoming messages + +check_hwm(Shaper = #lager_shaper{hwm = undefined}) -> + {true, 0, Shaper}; +check_hwm(Shaper = #lager_shaper{mps = Mps, hwm = Hwm}) when Mps < Hwm -> + %% haven't hit high water mark yet, just log it + {true, 0, Shaper#lager_shaper{mps=Mps+1}}; +check_hwm(Shaper = #lager_shaper{lasttime = Last, dropped = Drop}) -> + %% are we still in the same second? + {M, S, _} = Now = os:timestamp(), + case Last of + {M, S, _} -> + %% still in same second, but have exceeded the high water mark + NewDrops = discard_messages(Now, 0), + {false, 0, Shaper#lager_shaper{dropped=Drop+NewDrops}}; + _ -> + %% different second, reset all counters and allow it + {true, Drop, Shaper#lager_shaper{dropped = 0, mps=1, lasttime = Now}} + end. + +discard_messages(Second, Count) -> + {M, S, _} = os:timestamp(), + case Second of + {M, S, _} -> + receive + %% we only discard gen_event notifications, because + %% otherwise we might discard gen_event internal + %% messages, such as trapped EXITs + {notify, _Event} -> + discard_messages(Second, Count+1) + after 0 -> + Count + end; + _ -> + Count + end. + +%% @private Build an atom for the gen_event process based on a sink name. +%% For historical reasons, the default gen_event process for lager itself is named +%% `lager_event'. For all other sinks, it is SinkName++`_lager_event' +make_internal_sink_name(lager) -> + ?DEFAULT_SINK; +make_internal_sink_name(Sink) -> + list_to_atom(atom_to_list(Sink) ++ "_lager_event"). + -ifdef(TEST). parse_test() -> @@ -707,4 +779,28 @@ ?assertEqual([debug, notice, error], mask_to_levels(?DEBUG bor ?NOTICE bor ?ERROR)), ok. +expand_path_test() -> + OldRootVal = application:get_env(lager, log_root), + + ok = application:unset_env(lager, log_root), + ?assertEqual("/foo/bar", expand_path("/foo/bar")), + ?assertEqual("foo/bar", expand_path("foo/bar")), + + ok = application:set_env(lager, log_root, "log/dir"), + ?assertEqual("/foo/bar", expand_path("/foo/bar")), % Absolute path should not be changed + ?assertEqual("log/dir/foo/bar", expand_path("foo/bar")), + ?assertEqual("log/dir/foo/bar", expand_path("log/dir/foo/bar")), %% gh #304 + + case OldRootVal of + undefined -> application:unset_env(lager, log_root); + {ok, Root} -> application:set_env(lager, log_root, Root) + end, + ok. + +sink_name_test_() -> + [ + ?_assertEqual(lager_event, make_internal_sink_name(lager)), + ?_assertEqual(audit_lager_event, make_internal_sink_name(audit)) + ]. + -endif.
View file
lager-3.1.0.tar.gz/test/compress_pr_record_test.erl
Added
@@ -0,0 +1,16 @@ +-module(compress_pr_record_test). + +-compile([{parse_transform, lager_transform}]). + +-record(a, {field1, field2, foo, bar, baz, zyu, zix}). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + +nested_record_test() -> + A = #a{field1 = "Notice me senpai"}, + Pr_A = lager:pr(A, ?MODULE), + Pr_A_Comp = lager:pr(A, ?MODULE, [compress]), + ?assertMatch({'$lager_record', a, [{field1, "Notice me senpai"}, {field2, undefined} | _]}, Pr_A), + ?assertEqual({'$lager_record', a, [{field1, "Notice me senpai"}]}, Pr_A_Comp).
View file
lager-2.1.0.tar.gz/test/crash.erl -> lager-3.1.0.tar.gz/test/crash.erl
Changed
@@ -10,8 +10,8 @@ -export([start/0]). -record(state, { - host, - port + host :: term(), + port :: term() }). start() ->
View file
lager-3.1.0.tar.gz/test/lager_rotate.erl
Added
@@ -0,0 +1,138 @@ +-module(lager_rotate). + +-compile(export_all). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). +-endif. + + +rotate_test_() -> + {foreach, + fun() -> + file:write_file("test1.log", ""), + file:write_file("test2.log", ""), + file:write_file("test3.log", ""), + file:delete("test1.log.0"), + file:delete("test2.log.0"), + file:delete("test3.log.0"), + error_logger:tty(false), + application:load(lager), + application:set_env(lager, handlers, + [{lager_file_backend, [{file, "test1.log"}, {level, info}]}, + {lager_file_backend, [{file, "test2.log"}, {level, info}]}]), + application:set_env(lager, extra_sinks, + [{sink_event, + [{handlers, + [{lager_file_backend, [{file, "test3.log"}, {level, info}]}]} + ]}]), + application:set_env(lager, error_logger_redirect, false), + application:set_env(lager, async_threshold, undefined), + lager:start() + end, + fun(_) -> + file:delete("test1.log"), + file:delete("test2.log"), + file:delete("test3.log"), + file:delete("test1.log.0"), + file:delete("test2.log.0"), + file:delete("test3.log.0"), + application:stop(lager), + application:stop(goldrush), + error_logger:tty(true) + end, + [{"Rotate single file", + fun() -> + lager:log(error, self(), "Test message 1"), + lager:log(sink_event, error, self(), "Sink test message 1", []), + lager:rotate_handler({lager_file_backend, "test1.log"}), + timer:sleep(1000), + true = filelib:is_regular("test1.log.0"), + lager:log(error, self(), "Test message 2"), + lager:log(sink_event, error, self(), "Sink test message 2", []), + + {ok, File1} = file:read_file("test1.log"), + {ok, File2} = file:read_file("test2.log"), + {ok, SinkFile} = file:read_file("test3.log"), + {ok, File1Old} = file:read_file("test1.log.0"), + + have_no_log(File1, <<"Test message 1">>), + have_log(File1, <<"Test message 2">>), + + have_log(File2, <<"Test message 1">>), + have_log(File2, <<"Test message 2">>), + + have_log(File1Old, <<"Test message 1">>), + have_no_log(File1Old, <<"Test message 2">>), + + have_log(SinkFile, <<"Sink test message 1">>), + have_log(SinkFile, <<"Sink test message 2">>) + end}, + {"Rotate sink", + fun() -> + lager:log(error, self(), "Test message 1"), + lager:log(sink_event, error, self(), "Sink test message 1", []), + lager:rotate_sink(sink_event), + timer:sleep(1000), + true = filelib:is_regular("test3.log.0"), + lager:log(error, self(), "Test message 2"), + lager:log(sink_event, error, self(), "Sink test message 2", []), + {ok, File1} = file:read_file("test1.log"), + {ok, File2} = file:read_file("test2.log"), + {ok, SinkFile} = file:read_file("test3.log"), + {ok, SinkFileOld} = file:read_file("test3.log.0"), + + have_log(File1, <<"Test message 1">>), + have_log(File1, <<"Test message 2">>), + + have_log(File2, <<"Test message 1">>), + have_log(File2, <<"Test message 2">>), + + have_log(SinkFileOld, <<"Sink test message 1">>), + have_no_log(SinkFileOld, <<"Sink test message 2">>), + + have_no_log(SinkFile, <<"Sink test message 1">>), + have_log(SinkFile, <<"Sink test message 2">>) + end}, + {"Rotate all", + fun() -> + lager:log(error, self(), "Test message 1"), + lager:log(sink_event, error, self(), "Sink test message 1", []), + lager:rotate_all(), + timer:sleep(1000), + true = filelib:is_regular("test3.log.0"), + lager:log(error, self(), "Test message 2"), + lager:log(sink_event, error, self(), "Sink test message 2", []), + {ok, File1} = file:read_file("test1.log"), + {ok, File2} = file:read_file("test2.log"), + {ok, SinkFile} = file:read_file("test3.log"), + {ok, File1Old} = file:read_file("test1.log.0"), + {ok, File2Old} = file:read_file("test2.log.0"), + {ok, SinkFileOld} = file:read_file("test3.log.0"), + + have_no_log(File1, <<"Test message 1">>), + have_log(File1, <<"Test message 2">>), + + have_no_log(File2, <<"Test message 1">>), + have_log(File2, <<"Test message 2">>), + + have_no_log(SinkFile, <<"Sink test message 1">>), + have_log(SinkFile, <<"Sink test message 2">>), + + have_log(SinkFileOld, <<"Sink test message 1">>), + have_no_log(SinkFileOld, <<"Sink test message 2">>), + + have_log(File1Old, <<"Test message 1">>), + have_no_log(File1Old, <<"Test message 2">>), + + have_log(File2Old, <<"Test message 1">>), + have_no_log(File2Old, <<"Test message 2">>) + + end}]}. + +have_log(Data, Log) -> + {_,_} = binary:match(Data, Log). + +have_no_log(Data, Log) -> + nomatch = binary:match(Data, Log). +
View file
lager-2.1.0.tar.gz/test/lager_test_backend.erl -> lager-3.1.0.tar.gz/test/lager_test_backend.erl
Changed
@@ -1,4 +1,6 @@ -%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2011-2015 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -13,6 +15,8 @@ %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. +%% +%% ------------------------------------------------------------------- -module(lager_test_backend). @@ -23,12 +27,15 @@ -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3]). --record(state, {level, buffer, ignored}). --record(test, {attrs, format, args}). --compile([{parse_transform, lager_transform}]). +-define(TEST_SINK_NAME, '__lager_test_sink'). %% <-- used by parse transform +-define(TEST_SINK_EVENT, '__lager_test_sink_lager_event'). %% <-- used by lager API calls and internals for gen_event + +-record(state, {level :: list(), buffer :: list(), ignored :: term()}). +-compile({parse_transform, lager_transform}). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). +-record(test, {attrs :: list(), format :: list(), args :: list()}). -export([pop/0, count/0, count_ignored/0, flush/0, print_state/0]). -endif. @@ -89,28 +96,63 @@ -ifdef(TEST). pop() -> - gen_event:call(lager_event, ?MODULE, pop). + pop(lager_event). count() -> - gen_event:call(lager_event, ?MODULE, count). + count(lager_event). count_ignored() -> - gen_event:call(lager_event, ?MODULE, count_ignored). + count_ignored(lager_event). flush() -> - gen_event:call(lager_event, ?MODULE, flush). + flush(lager_event). print_state() -> - gen_event:call(lager_event, ?MODULE, print_state). + print_state(lager_event). print_bad_state() -> - gen_event:call(lager_event, ?MODULE, print_bad_state). + print_bad_state(lager_event). + +pop(Sink) -> + gen_event:call(Sink, ?MODULE, pop). + +count(Sink) -> + gen_event:call(Sink, ?MODULE, count). + +count_ignored(Sink) -> + gen_event:call(Sink, ?MODULE, count_ignored). + +flush(Sink) -> + gen_event:call(Sink, ?MODULE, flush). + +print_state(Sink) -> + gen_event:call(Sink, ?MODULE, print_state). + +print_bad_state(Sink) -> + gen_event:call(Sink, ?MODULE, print_bad_state). + has_line_numbers() -> %% are we R15 or greater - Rel = erlang:system_info(otp_release), - {match, [Major]} = re:run(Rel, "(?|(^R(\\d+)[A|B](|0(\\d)))|(^(\\d+)$))", [{capture, [2], list}]), - list_to_integer(Major) >= 15. + % this gets called a LOT - cache the answer + case erlang:get({?MODULE, has_line_numbers}) of + undefined -> + R = otp_version() >= 15, + erlang:put({?MODULE, has_line_numbers}, R), + R; + Bool -> + Bool + end. + +otp_version() -> + otp_version(erlang:system_info(otp_release)). + +otp_version([$R | Rel]) -> + {Ver, _} = string:to_integer(Rel), + Ver; +otp_version(Rel) -> + {Ver, _} = string:to_integer(Rel), + Ver. not_running_test() -> ?assertEqual({error, lager_not_running}, lager:log(info, self(), "not running")). @@ -126,6 +168,11 @@ ?assertEqual(0, count()) end }, + {"test sink not running", + fun() -> + ?assertEqual({error, {sink_not_configured, test}}, lager:log(test, info, self(), "~p", "not running")) + end + }, {"logging works", fun() -> lager:warning("test message"), @@ -136,6 +183,16 @@ ok end }, + {"unsafe logging works", + fun() -> + lager:warning_unsafe("test message"), + ?assertEqual(1, count()), + {Level, _Time, Message, _Metadata} = pop(), + ?assertMatch(Level, lager_util:level_to_num(warning)), + ?assertEqual("test message", Message), + ok + end + }, {"logging with arguments works", fun() -> lager:warning("test message ~p", [self()]), @@ -146,6 +203,16 @@ ok end }, + {"unsafe logging with args works", + fun() -> + lager:warning("test message ~p", [self()]), + ?assertEqual(1, count()), + {Level, _Time, Message,_Metadata} = pop(), + ?assertMatch(Level, lager_util:level_to_num(warning)), + ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)), + ok + end + }, {"logging works from inside a begin/end block", fun() -> ?assertEqual(0, count()), @@ -469,6 +536,39 @@ ok end }, + {"stopped trace stops and removes its event handler - default sink (gh#267)", + fun() -> + Sink = ?DEFAULT_SINK, + StartHandlers = gen_event:which_handlers(Sink), + {_, T0} = lager_config:get({Sink, loglevel}), + StartGlobal = lager_config:global_get(handlers), + ?assertEqual([], T0), + {ok, TestTrace1} = lager:trace_file("/tmp/test", [{a,b}]), + MidHandlers = gen_event:which_handlers(Sink), + {ok, TestTrace2} = lager:trace_file("/tmp/test", [{c,d}]), + MidHandlers = gen_event:which_handlers(Sink), + ?assertEqual(length(StartHandlers)+1, length(MidHandlers)), + MidGlobal = lager_config:global_get(handlers), + ?assertEqual(length(StartGlobal)+1, length(MidGlobal)), + {_, T1} = lager_config:get({Sink, loglevel}), + ?assertEqual(2, length(T1)), + ok = lager:stop_trace(TestTrace1), + {_, T2} = lager_config:get({Sink, loglevel}), + ?assertEqual(1, length(T2)), + ?assertEqual(length(StartHandlers)+1, length( + gen_event:which_handlers(Sink))), + + ?assertEqual(length(StartGlobal)+1, length(lager_config:global_get(handlers))), + ok = lager:stop_trace(TestTrace2), + EndHandlers = gen_event:which_handlers(?DEFAULT_SINK), + EndGlobal = lager_config:global_get(handlers), + {_, T3} = lager_config:get({Sink, loglevel}), + ?assertEqual([], T3), + ?assertEqual(StartHandlers, EndHandlers), + ?assertEqual(StartGlobal, EndGlobal), + ok + end + }, {"record printing works", fun() -> print_state(), @@ -545,6 +645,13 @@ ok end }, + {"unsafe messages really are not truncated", + fun() -> + lager:info_unsafe("doom, doom has come upon you all ~p", [string:copies("doom", 1500)]), + {_, _, Msg,_Metadata} = pop(), + ?assert(length(lists:flatten(Msg)) == 6035) + end + }, {"can't store invalid metadata", fun() -> ?assertEqual(ok, lager:md([{platypus, gravid}, {sloth, hirsute}, {duck, erroneous}])), @@ -556,6 +663,132 @@ ] }. +extra_sinks_test_() -> + {foreach, + fun setup_sink/0, + fun cleanup/1, + [ + {"observe that there is nothing up my sleeve", + fun() -> + ?assertEqual(undefined, pop(?TEST_SINK_EVENT)), + ?assertEqual(0, count(?TEST_SINK_EVENT)) + end + }, + {"logging works", + fun() -> + ?TEST_SINK_NAME:warning("test message"), + ?assertEqual(1, count(?TEST_SINK_EVENT)), + {Level, _Time, Message, _Metadata} = pop(?TEST_SINK_EVENT), + ?assertMatch(Level, lager_util:level_to_num(warning)), + ?assertEqual("test message", Message), + ok + end + }, + {"logging with arguments works", + fun() -> + ?TEST_SINK_NAME:warning("test message ~p", [self()]), + ?assertEqual(1, count(?TEST_SINK_EVENT)), + {Level, _Time, Message,_Metadata} = pop(?TEST_SINK_EVENT), + ?assertMatch(Level, lager_util:level_to_num(warning)), + ?assertEqual(lists:flatten(io_lib:format("test message ~p", [self()])), lists:flatten(Message)), + ok + end + }, + {"variables inplace of literals in logging statements work", + fun() -> + ?assertEqual(0, count(?TEST_SINK_EVENT)), + Attr = [{a, alpha}, {b, beta}], + Fmt = "format ~p", + Args = [world], + ?TEST_SINK_NAME:info(Attr, "hello"), + ?TEST_SINK_NAME:info(Attr, "hello ~p", [world]), + ?TEST_SINK_NAME:info(Fmt, [world]), + ?TEST_SINK_NAME:info("hello ~p", Args), + ?TEST_SINK_NAME:info(Attr, "hello ~p", Args), + ?TEST_SINK_NAME:info([{d, delta}, {g, gamma}], Fmt, Args), + ?assertEqual(6, count(?TEST_SINK_EVENT)), + {_Level, _Time, Message, Metadata} = pop(?TEST_SINK_EVENT), + ?assertMatch([{a, alpha}, {b, beta}|_], Metadata), + ?assertEqual("hello", lists:flatten(Message)), + {_Level, _Time2, Message2, _Metadata2} = pop(?TEST_SINK_EVENT), + ?assertEqual("hello world", lists:flatten(Message2)), + {_Level, _Time3, Message3, _Metadata3} = pop(?TEST_SINK_EVENT), + ?assertEqual("format world", lists:flatten(Message3)), + {_Level, _Time4, Message4, _Metadata4} = pop(?TEST_SINK_EVENT), + ?assertEqual("hello world", lists:flatten(Message4)), + {_Level, _Time5, Message5, _Metadata5} = pop(?TEST_SINK_EVENT), + ?assertEqual("hello world", lists:flatten(Message5)), + {_Level, _Time6, Message6, Metadata6} = pop(?TEST_SINK_EVENT), + ?assertMatch([{d, delta}, {g, gamma}|_], Metadata6), + ?assertEqual("format world", lists:flatten(Message6)), + ok + end + }, + {"stopped trace stops and removes its event handler - test sink (gh#267)", + fun() -> + Sink = ?TEST_SINK_EVENT, + StartHandlers = gen_event:which_handlers(Sink), + {_, T0} = lager_config:get({Sink, loglevel}), + StartGlobal = lager_config:global_get(handlers), + ?assertEqual([], T0), + {ok, TestTrace1} = lager:trace_file("/tmp/test", [{sink, Sink}, {a,b}]), + MidHandlers = gen_event:which_handlers(Sink), + {ok, TestTrace2} = lager:trace_file("/tmp/test", [{sink, Sink}, {c,d}]), + MidHandlers = gen_event:which_handlers(Sink), + ?assertEqual(length(StartHandlers)+1, length(MidHandlers)), + MidGlobal = lager_config:global_get(handlers), + ?assertEqual(length(StartGlobal)+1, length(MidGlobal)), + {_, T1} = lager_config:get({Sink, loglevel}), + ?assertEqual(2, length(T1)), + ok = lager:stop_trace(TestTrace1), + {_, T2} = lager_config:get({Sink, loglevel}), + ?assertEqual(1, length(T2)), + ?assertEqual(length(StartHandlers)+1, length( + gen_event:which_handlers(Sink))), + + ?assertEqual(length(StartGlobal)+1, length(lager_config:global_get(handlers))), + ok = lager:stop_trace(TestTrace2), + EndHandlers = gen_event:which_handlers(Sink), + EndGlobal = lager_config:global_get(handlers), + {_, T3} = lager_config:get({Sink, loglevel}), + ?assertEqual([], T3), + ?assertEqual(StartHandlers, EndHandlers), + ?assertEqual(StartGlobal, EndGlobal), + ok + end + }, + {"log messages below the threshold are ignored", + fun() -> + ?assertEqual(0, count(?TEST_SINK_EVENT)), + ?TEST_SINK_NAME:debug("this message will be ignored"), + ?assertEqual(0, count(?TEST_SINK_EVENT)), + ?assertEqual(0, count_ignored(?TEST_SINK_EVENT)), + lager_config:set({?TEST_SINK_EVENT, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}), + ?TEST_SINK_NAME:debug("this message should be ignored"), + ?assertEqual(0, count(?TEST_SINK_EVENT)), + ?assertEqual(1, count_ignored(?TEST_SINK_EVENT)), + lager:set_loglevel(?TEST_SINK_EVENT, ?MODULE, undefined, debug), + ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({?TEST_SINK_EVENT, loglevel})), + ?TEST_SINK_NAME:debug("this message should be logged"), + ?assertEqual(1, count(?TEST_SINK_EVENT)), + ?assertEqual(1, count_ignored(?TEST_SINK_EVENT)), + ?assertEqual(debug, lager:get_loglevel(?TEST_SINK_EVENT, ?MODULE)), + ok + end + } + ] + }. + +setup_sink() -> + error_logger:tty(false), + application:load(lager), + application:set_env(lager, handlers, []), + application:set_env(lager, error_logger_redirect, false), + application:set_env(lager, extra_sinks, [{?TEST_SINK_EVENT, [{handlers, [{?MODULE, info}]}]}]), + lager:start(), + gen_event:call(lager_event, ?MODULE, flush), + gen_event:call(?TEST_SINK_EVENT, ?MODULE, flush). + setup() -> error_logger:tty(false), application:load(lager), @@ -579,107 +812,171 @@ test_body(Expected, Actual) -> case has_line_numbers() of true -> - FileLine = string:substr(Actual, length(Expected)+1), - Body = string:substr(Actual, 1, length(Expected)), + ExLen = length(Expected), + {Body, Rest} = case length(Actual) > ExLen of + true -> + {string:substr(Actual, 1, ExLen), + string:substr(Actual, (ExLen + 1))}; + _ -> + {Actual, []} + end, ?assertEqual(Expected, Body), - case string:substr(FileLine, 1, 6) of + % OTP-17 (and maybe later releases) may tack on additional info + % about the failure, so if Actual starts with Expected (already + % confirmed by having gotten past assertEqual above) and ends + % with " line NNN" we can ignore what's in-between. By extension, + % since there may not be line information appended at all, any + % text we DO find is reportable, but not a test failure. + case Rest of [] -> - %% sometimes there's no line information... - ?assert(true); - " line " -> - ?assert(true); - Other -> - ?debugFmt("unexpected trailing data ~p", [Other]), - ?assert(false) + ok; + _ -> + % isolate the extra data and report it if it's not just + % a line number indicator + case re:run(Rest, "^.*( line \\d+)$", [{capture, [1]}]) of + nomatch -> + ?debugFmt( + "Trailing data \"~s\" following \"~s\"", + [Rest, Expected]); + {match, [{0, _}]} -> + % the whole sting is " line NNN" + ok; + {match, [{Off, _}]} -> + ?debugFmt( + "Trailing data \"~s\" following \"~s\"", + [string:substr(Rest, 1, Off), Expected]) + end end; - false -> + _ -> ?assertEqual(Expected, Actual) end. +error_logger_redirect_crash_setup() -> + error_logger:tty(false), + application:load(lager), + application:set_env(lager, error_logger_redirect, true), + application:set_env(lager, handlers, [{?MODULE, error}]), + lager:start(), + crash:start(), + lager_event. + +error_logger_redirect_crash_setup_sink() -> + error_logger:tty(false), + application:load(lager), + application:set_env(lager, error_logger_redirect, true), + application:unset_env(lager, handlers), + application:set_env(lager, extra_sinks, [ + {error_logger_lager_event, [ + {handlers, [{?MODULE, error}]}]}]), + lager:start(), + crash:start(), + error_logger_lager_event. + +error_logger_redirect_crash_cleanup(_Sink) -> + application:stop(lager), + application:stop(goldrush), + application:unset_env(lager, extra_sinks), + case whereis(crash) of + undefined -> ok; + Pid -> exit(Pid, kill) + end, + error_logger:tty(true). error_logger_redirect_crash_test_() -> - TestBody=fun(Name,CrashReason,Expected) -> {Name, + TestBody=fun(Name,CrashReason,Expected) -> + fun(Sink) -> + {Name, fun() -> Pid = whereis(crash), crash(CrashReason), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), test_body(Expected, lists:flatten(Msg)), ?assertEqual(Pid,proplists:get_value(pid,Metadata)), ?assertEqual(lager_util:level_to_num(error),Level) end - } + } + end end, - {foreach, - fun() -> - error_logger:tty(false), - application:load(lager), - application:set_env(lager, error_logger_redirect, true), - application:set_env(lager, handlers, [{?MODULE, error}]), - lager:start(), - crash:start() - end, - - fun(_) -> - application:stop(lager), - application:stop(goldrush), - case whereis(crash) of - undefined -> ok; - Pid -> exit(Pid, kill) - end, - error_logger:tty(true) - end, - [ + Tests = [ + fun(Sink) -> {"again, there is nothing up my sleeve", fun() -> - ?assertEqual(undefined, pop()), - ?assertEqual(0, count()) + ?assertEqual(undefined, pop(Sink)), + ?assertEqual(0, count(Sink)) end - }, + } + end, - TestBody("bad return value",bad_return,"gen_server crash terminated with reason: bad return value: bleh"), - TestBody("bad return value with string",bad_return_string,"gen_server crash terminated with reason: bad return value: {tuple,{tuple,\"string\"}}"), - TestBody("bad return uncaught throw",throw,"gen_server crash terminated with reason: bad return value: a_ball"), - TestBody("case clause",case_clause,"gen_server crash terminated with reason: no case clause matching {} in crash:handle_call/3"), - TestBody("case clause string",case_clause_string,"gen_server crash terminated with reason: no case clause matching \"crash\" in crash:handle_call/3"), - TestBody("function clause",function_clause,"gen_server crash terminated with reason: no function clause matching crash:function({})"), - TestBody("if clause",if_clause,"gen_server crash terminated with reason: no true branch found while evaluating if expression in crash:handle_call/3"), - TestBody("try clause",try_clause,"gen_server crash terminated with reason: no try clause matching [] in crash:handle_call/3"), - TestBody("undefined function",undef,"gen_server crash terminated with reason: call to undefined function crash:booger/0 from crash:handle_call/3"), - TestBody("bad math",badarith,"gen_server crash terminated with reason: bad arithmetic expression in crash:handle_call/3"), - TestBody("bad match",badmatch,"gen_server crash terminated with reason: no match of right hand value {} in crash:handle_call/3"), - TestBody("bad arity",badarity,"gen_server crash terminated with reason: fun called with wrong arity of 1 instead of 3 in crash:handle_call/3"), - TestBody("bad arg1",badarg1,"gen_server crash terminated with reason: bad argument in crash:handle_call/3"), - TestBody("bad arg2",badarg2,"gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([\"foo\",bar]) in crash:handle_call/3"), - TestBody("bad record",badrecord,"gen_server crash terminated with reason: bad record state in crash:handle_call/3"), - TestBody("noproc",noproc,"gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)"), - TestBody("badfun",badfun,"gen_server crash terminated with reason: bad function booger in crash:handle_call/3") - ] - }. + TestBody("bad return value",bad_return,"gen_server crash terminated with reason: bad return value: bleh"), + TestBody("bad return value with string",bad_return_string,"gen_server crash terminated with reason: bad return value: {tuple,{tuple,\"string\"}}"), + TestBody("bad return uncaught throw",throw,"gen_server crash terminated with reason: bad return value: a_ball"), + TestBody("case clause",case_clause,"gen_server crash terminated with reason: no case clause matching {} in crash:handle_call/3"), + TestBody("case clause string",case_clause_string,"gen_server crash terminated with reason: no case clause matching \"crash\" in crash:handle_call/3"), + TestBody("function clause",function_clause,"gen_server crash terminated with reason: no function clause matching crash:function({})"), + TestBody("if clause",if_clause,"gen_server crash terminated with reason: no true branch found while evaluating if expression in crash:handle_call/3"), + TestBody("try clause",try_clause,"gen_server crash terminated with reason: no try clause matching [] in crash:handle_call/3"), + TestBody("undefined function",undef,"gen_server crash terminated with reason: call to undefined function crash:booger/0 from crash:handle_call/3"), + TestBody("bad math",badarith,"gen_server crash terminated with reason: bad arithmetic expression in crash:handle_call/3"), + TestBody("bad match",badmatch,"gen_server crash terminated with reason: no match of right hand value {} in crash:handle_call/3"), + TestBody("bad arity",badarity,"gen_server crash terminated with reason: fun called with wrong arity of 1 instead of 3 in crash:handle_call/3"), + TestBody("bad arg1",badarg1,"gen_server crash terminated with reason: bad argument in crash:handle_call/3"), + TestBody("bad arg2",badarg2,"gen_server crash terminated with reason: bad argument in call to erlang:iolist_to_binary([\"foo\",bar]) in crash:handle_call/3"), + TestBody("bad record",badrecord,"gen_server crash terminated with reason: bad record state in crash:handle_call/3"), + TestBody("noproc",noproc,"gen_server crash terminated with reason: no such process or port in call to gen_event:call(foo, bar, baz)"), + TestBody("badfun",badfun,"gen_server crash terminated with reason: bad function booger in crash:handle_call/3") + ], + {"Error logger redirect crash", [ + {"Redirect to default sink", + {foreach, + fun error_logger_redirect_crash_setup/0, + fun error_logger_redirect_crash_cleanup/1, + Tests}}, + {"Redirect to error_logger_lager_event sink", + {foreach, + fun error_logger_redirect_crash_setup_sink/0, + fun error_logger_redirect_crash_cleanup/1, + Tests}} + ]}. -error_logger_redirect_test_() -> - {foreach, - fun() -> - error_logger:tty(false), - application:load(lager), - application:set_env(lager, error_logger_redirect, true), - application:set_env(lager, handlers, [{?MODULE, info}]), - lager:start(), - lager:log(error, self(), "flush flush"), - timer:sleep(100), - gen_event:call(lager_event, ?MODULE, flush) - end, - fun(_) -> - application:stop(lager), - application:stop(goldrush), - error_logger:tty(true) - end, - [ +error_logger_redirect_setup() -> + error_logger:tty(false), + application:load(lager), + application:set_env(lager, error_logger_redirect, true), + application:set_env(lager, handlers, [{?MODULE, info}]), + lager:start(), + lager:log(error, self(), "flush flush"), + timer:sleep(100), + gen_event:call(lager_event, ?MODULE, flush), + lager_event. + +error_logger_redirect_setup_sink() -> + error_logger:tty(false), + application:load(lager), + application:set_env(lager, error_logger_redirect, true), + application:unset_env(lager, handlers), + application:set_env(lager, extra_sinks, [ + {error_logger_lager_event, [ + {handlers, [{?MODULE, info}]}]}]), + lager:start(), + lager:log(error_logger_lager_event, error, self(), "flush flush", []), + timer:sleep(100), + gen_event:call(error_logger_lager_event, ?MODULE, flush), + error_logger_lager_event. + +error_logger_redirect_cleanup(_) -> + application:stop(lager), + application:stop(goldrush), + application:unset_env(lager, extra_sinks), + error_logger:tty(true). + +error_logger_redirect_test_() -> + Tests = [ {"error reports are printed", - fun() -> + fun(Sink) -> sync_error_logger:error_report([{this, is}, a, {silly, format}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "this: is, a, silly: format", @@ -688,10 +985,10 @@ end }, {"string error reports are printed", - fun() -> + fun(Sink) -> sync_error_logger:error_report("this is less silly"), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "this is less silly", @@ -699,29 +996,40 @@ end }, {"error messages are printed", - fun() -> + fun(Sink) -> sync_error_logger:error_msg("doom, doom has come upon you all"), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "doom, doom has come upon you all", ?assertEqual(Expected, lists:flatten(Msg)) end }, + {"error messages with unicode characters in Args are printed", + fun(Sink) -> + sync_error_logger:error_msg("~ts", ["Привет!"]), + _ = gen_event:which_handlers(error_logger), + {Level, _, Msg,Metadata} = pop(Sink), + ?assertEqual(lager_util:level_to_num(error),Level), + ?assertEqual(self(),proplists:get_value(pid,Metadata)), + ?assertEqual("Привет!", lists:flatten(Msg)) + end + }, {"error messages are truncated at 4096 characters", - fun() -> + fun(Sink) -> sync_error_logger:error_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]), _ = gen_event:which_handlers(error_logger), - {_, _, Msg,_Metadata} = pop(), + {_, _, Msg,_Metadata} = pop(Sink), ?assert(length(lists:flatten(Msg)) < 5100) end }, + {"info reports are printed", - fun() -> + fun(Sink) -> sync_error_logger:info_report([{this, is}, a, {silly, format}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = "this: is, a, silly: format", @@ -729,144 +1037,165 @@ end }, {"info reports are truncated at 4096 characters", - fun() -> + fun(Sink) -> sync_error_logger:info_report([[{this, is}, a, {silly, format}] || _ <- lists:seq(0, 600)]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assert(length(lists:flatten(Msg)) < 5000) end }, {"single term info reports are printed", - fun() -> + fun(Sink) -> sync_error_logger:info_report({foolish, bees}), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("{foolish,bees}", lists:flatten(Msg)) end }, {"single term error reports are printed", - fun() -> + fun(Sink) -> sync_error_logger:error_report({foolish, bees}), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("{foolish,bees}", lists:flatten(Msg)) end }, {"string info reports are printed", - fun() -> + fun(Sink) -> sync_error_logger:info_report("this is less silly"), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("this is less silly", lists:flatten(Msg)) end }, {"string info reports are truncated at 4096 characters", - fun() -> + fun(Sink) -> sync_error_logger:info_report(string:copies("this is less silly", 1000)), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assert(length(lists:flatten(Msg)) < 5100) end }, {"strings in a mixed report are printed as strings", - fun() -> + fun(Sink) -> sync_error_logger:info_report(["this is less silly", {than, "this"}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("\"this is less silly\", than: \"this\"", lists:flatten(Msg)) end }, {"info messages are printed", - fun() -> + fun(Sink) -> sync_error_logger:info_msg("doom, doom has come upon you all"), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("doom, doom has come upon you all", lists:flatten(Msg)) end }, {"info messages are truncated at 4096 characters", - fun() -> + fun(Sink) -> sync_error_logger:info_msg("doom, doom has come upon you all ~p", [string:copies("doom", 10000)]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assert(length(lists:flatten(Msg)) < 5100) end }, + {"info messages with unicode characters in Args are printed", + fun(Sink) -> + sync_error_logger:info_msg("~ts", ["Привет!"]), + _ = gen_event:which_handlers(error_logger), + {Level, _, Msg,Metadata} = pop(Sink), + ?assertEqual(lager_util:level_to_num(info),Level), + ?assertEqual(self(),proplists:get_value(pid,Metadata)), + ?assertEqual("Привет!", lists:flatten(Msg)) + end + }, + {"warning messages with unicode characters in Args are printed", + fun(Sink) -> + sync_error_logger:warning_msg("~ts", ["Привет!"]), + Map = error_logger:warning_map(), + _ = gen_event:which_handlers(error_logger), + {Level, _, Msg,Metadata} = pop(Sink), + ?assertEqual(lager_util:level_to_num(Map),Level), + ?assertEqual(self(),proplists:get_value(pid,Metadata)), + ?assertEqual("Привет!", lists:flatten(Msg)) + end + }, {"warning messages are printed at the correct level", - fun() -> + fun(Sink) -> sync_error_logger:warning_msg("doom, doom has come upon you all"), Map = error_logger:warning_map(), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(Map),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("doom, doom has come upon you all", lists:flatten(Msg)) end }, {"warning reports are printed at the correct level", - fun() -> + fun(Sink) -> sync_error_logger:warning_report([{i, like}, pie]), Map = error_logger:warning_map(), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(Map),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("i: like, pie", lists:flatten(Msg)) end }, {"single term warning reports are printed at the correct level", - fun() -> + fun(Sink) -> sync_error_logger:warning_report({foolish, bees}), Map = error_logger:warning_map(), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(Map),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("{foolish,bees}", lists:flatten(Msg)) end }, {"application stop reports", - fun() -> + fun(Sink) -> sync_error_logger:info_report([{application, foo}, {exited, quittin_time}, {type, lazy}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Application foo exited with reason: quittin_time", lists:flatten(Msg)) end }, {"supervisor reports", - fun() -> + fun(Sink) -> sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason fired in context france", lists:flatten(Msg)) end }, {"supervisor reports with real error", - fun() -> + fun(Sink) -> sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, {function_clause,[{crash,handle_info,[foo]}]}}, {supervisor, {local, steve}}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor steve had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", lists:flatten(Msg)) @@ -874,10 +1203,10 @@ }, {"supervisor reports with real error and pid", - fun() -> + fun(Sink) -> sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{name, mini_steve}, {mfargs, {a, b, [c]}}, {pid, bleh}]}, {reason, {function_clause,[{crash,handle_info,[foo]}]}}, {supervisor, somepid}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor somepid had child mini_steve started with a:b(c) at bleh exit with reason no function clause matching crash:handle_info(foo) in context france", lists:flatten(Msg)) @@ -885,20 +1214,20 @@ }, {"supervisor_bridge reports", - fun() -> + fun(Sink) -> sync_error_logger:error_report(supervisor_report, [{errorContext, france}, {offender, [{mod, mini_steve}, {pid, bleh}]}, {reason, fired}, {supervisor, {local, steve}}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor steve had child at module mini_steve at bleh exit with reason fired in context france", lists:flatten(Msg)) end }, {"application progress report", - fun() -> + fun(Sink) -> sync_error_logger:info_report(progress, [{application, foo}, {started_at, node()}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(info),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("Application foo started on node ~w", [node()])), @@ -906,34 +1235,34 @@ end }, {"supervisor progress report", - fun() -> - lager:set_loglevel(?MODULE, debug), - ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), + fun(Sink) -> + lager:set_loglevel(Sink, ?MODULE, undefined, debug), + ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report(progress, [{supervisor, {local, foo}}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(debug),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor foo started foo:bar/1 at pid baz", lists:flatten(Msg)) end }, {"supervisor progress report with pid", - fun() -> - lager:set_loglevel(?MODULE, debug), - ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), + fun(Sink) -> + lager:set_loglevel(Sink, ?MODULE, undefined, debug), + ?assertEqual({?DEBUG bor ?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report(progress, [{supervisor, somepid}, {started, [{mfargs, {foo, bar, 1}}, {pid, baz}]}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(debug),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Supervisor somepid started foo:bar/1 at pid baz", lists:flatten(Msg)) end }, {"crash report for emfile", - fun() -> + fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, emfile, [{stack, trace, 1}]}}], []]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: maximum number of file descriptors exhausted, check ulimit -n", [self()])), @@ -941,10 +1270,10 @@ end }, {"crash report for system process limit", - fun() -> + fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, spawn, 1}]}}], []]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self()])), @@ -952,10 +1281,10 @@ end }, {"crash report for system process limit2", - fun() -> + fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, spawn_opt, 1}]}}], []]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of processes exceeded", [self()])), @@ -963,10 +1292,10 @@ end }, {"crash report for system port limit", - fun() -> + fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, open_port, 1}]}}], []]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ports exceeded", [self()])), @@ -974,10 +1303,10 @@ end }, {"crash report for system port limit", - fun() -> + fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, []}, {error_info, {error, system_limit, [{erlang, list_to_atom, 1}]}}], []]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: tried to create an atom larger than 255, or maximum atom count exceeded", [self()])), @@ -985,10 +1314,10 @@ end }, {"crash report for system ets table limit", - fun() -> + fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {registered_name, test}, {error_info, {error, system_limit, [{ets,new,[segment_offsets,[ordered_set,public]]},{mi_segment,open_write,1},{mi_buffer_converter,handle_cast,2},{gen_server,handle_msg,5},{proc_lib,init_p_do_apply,3}]}}], []]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~w with 0 neighbours crashed with reason: system limit: maximum number of ETS tables exceeded", [test])), @@ -996,75 +1325,75 @@ end }, {"crash report for unknown system limit should be truncated at 500 characters", - fun() -> + fun(Sink) -> sync_error_logger:error_report(crash_report, [[{pid, self()}, {error_info, {error, system_limit, [{wtf,boom,[string:copies("aaaa", 4096)]}]}}], []]), _ = gen_event:which_handlers(error_logger), - {_, _, Msg,_Metadata} = pop(), + {_, _, Msg,_Metadata} = pop(Sink), ?assert(length(lists:flatten(Msg)) > 550), ?assert(length(lists:flatten(Msg)) < 600) end }, - {"crash reports for 'special processes' should be handled right - function_clause", - fun() -> + {"crash reports for 'special processes' should be handled right - function_clause", + fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! function_clause, timer:sleep(500), _ = gen_event:which_handlers(error_logger), - {_, _, Msg, _Metadata} = pop(), + {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: no function clause matching special_process:foo(bar)", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, - {"crash reports for 'special processes' should be handled right - case_clause", - fun() -> + {"crash reports for 'special processes' should be handled right - case_clause", + fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! {case_clause, wtf}, timer:sleep(500), _ = gen_event:which_handlers(error_logger), - {_, _, Msg, _Metadata} = pop(), + {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: no case clause matching wtf in special_process:loop/0", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, - {"crash reports for 'special processes' should be handled right - exit", - fun() -> + {"crash reports for 'special processes' should be handled right - exit", + fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! exit, timer:sleep(500), _ = gen_event:which_handlers(error_logger), - {_, _, Msg, _Metadata} = pop(), + {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours exited with reason: byebye in special_process:loop/0", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, - {"crash reports for 'special processes' should be handled right - error", - fun() -> + {"crash reports for 'special processes' should be handled right - error", + fun(Sink) -> {ok, Pid} = special_process:start(), unlink(Pid), Pid ! error, timer:sleep(500), _ = gen_event:which_handlers(error_logger), - {_, _, Msg, _Metadata} = pop(), + {_, _, Msg, _Metadata} = pop(Sink), Expected = lists:flatten(io_lib:format("CRASH REPORT Process ~p with 0 neighbours crashed with reason: mybad in special_process:loop/0", [Pid])), test_body(Expected, lists:flatten(Msg)) end }, {"webmachine error reports", - fun() -> + fun(Sink) -> Path = "/cgi-bin/phpmyadmin", Reason = {error,{error,{badmatch,{error,timeout}}, [{myapp,dostuff,2,[{file,"src/myapp.erl"},{line,123}]}, {webmachine_resource,resource_call,3,[{file,"src/webmachine_resource.erl"},{line,169}]}]}}, sync_error_logger:error_msg("webmachine error: path=~p~n~p~n", [Path, Reason]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Webmachine error at path \"/cgi-bin/phpmyadmin\" : no match of right hand value {error,timeout} in myapp:dostuff/2 line 123", lists:flatten(Msg)) @@ -1072,7 +1401,7 @@ end }, {"Cowboy error reports, 8 arg version", - fun() -> + fun(Sink) -> Stack = [{my_handler,init, 3,[{file,"src/my_handler.erl"},{line,123}]}, {cowboy_handler,handler_init,4,[{file,"src/cowboy_handler.erl"},{line,169}]}], @@ -1085,14 +1414,14 @@ [my_handler, init, 3, error, {badmatch, {error, timeout}}, [], "Request", Stack]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Cowboy handler my_handler terminated in my_handler:init/3 with reason: no match of right hand value {error,timeout} in my_handler:init/3 line 123", lists:flatten(Msg)) end }, {"Cowboy error reports, 10 arg version", - fun() -> + fun(Sink) -> Stack = [{my_handler,somecallback, 3,[{file,"src/my_handler.erl"},{line,123}]}, {cowboy_handler,handler_init,4,[{file,"src/cowboy_handler.erl"},{line,169}]}], sync_error_logger:error_msg( @@ -1104,61 +1433,85 @@ {}, "Request", Stack]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Cowboy handler my_handler terminated in my_handler:somecallback/3 with reason: no match of right hand value {error,timeout} in my_handler:somecallback/3 line 123", lists:flatten(Msg)) end }, {"Cowboy error reports, 5 arg version", - fun() -> + fun(Sink) -> sync_error_logger:error_msg( "** Cowboy handler ~p terminating; " "function ~p/~p was not exported~n" "** Request was ~p~n** State was ~p~n~n", [my_handler, to_json, 2, "Request", {}]), _ = gen_event:which_handlers(error_logger), - {Level, _, Msg,Metadata} = pop(), + {Level, _, Msg,Metadata} = pop(Sink), ?assertEqual(lager_util:level_to_num(error),Level), ?assertEqual(self(),proplists:get_value(pid,Metadata)), ?assertEqual("Cowboy handler my_handler terminated with reason: call to undefined function my_handler:to_json/2", lists:flatten(Msg)) end }, {"messages should not be generated if they don't satisfy the threshold", - fun() -> - lager:set_loglevel(?MODULE, error), - ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), + fun(Sink) -> + lager:set_loglevel(Sink, ?MODULE, undefined, error), + ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report([hello, world]), _ = gen_event:which_handlers(error_logger), - ?assertEqual(0, count()), - ?assertEqual(0, count_ignored()), - lager:set_loglevel(?MODULE, info), - ?assertEqual({?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), + ?assertEqual(0, count(Sink)), + ?assertEqual(0, count_ignored(Sink)), + lager:set_loglevel(Sink, ?MODULE, undefined, info), + ?assertEqual({?INFO bor ?NOTICE bor ?WARNING bor ?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), sync_error_logger:info_report([hello, world]), _ = gen_event:which_handlers(error_logger), - ?assertEqual(1, count()), - ?assertEqual(0, count_ignored()), - lager:set_loglevel(?MODULE, error), - ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get(loglevel)), - lager_config:set(loglevel, {element(2, lager_util:config_to_mask(debug)), []}), + ?assertEqual(1, count(Sink)), + ?assertEqual(0, count_ignored(Sink)), + lager:set_loglevel(Sink, ?MODULE, undefined, error), + ?assertEqual({?ERROR bor ?CRITICAL bor ?ALERT bor ?EMERGENCY, []}, lager_config:get({Sink, loglevel})), + lager_config:set({Sink, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}), sync_error_logger:info_report([hello, world]), _ = gen_event:which_handlers(error_logger), - ?assertEqual(1, count()), - ?assertEqual(1, count_ignored()) + ?assertEqual(1, count(Sink)), + ?assertEqual(1, count_ignored(Sink)) end } - ] - }. + ], + SinkTests = lists:map( + fun({Name, F}) -> + fun(Sink) -> {Name, fun() -> F(Sink) end} end + end, + Tests), + {"Error logger redirect", [ + {"Redirect to default sink", + {foreach, + fun error_logger_redirect_setup/0, + fun error_logger_redirect_cleanup/1, + SinkTests}}, + {"Redirect to error_logger_lager_event sink", + {foreach, + fun error_logger_redirect_setup_sink/0, + fun error_logger_redirect_cleanup/1, + SinkTests}} + ]}. safe_format_test() -> ?assertEqual("foo bar", lists:flatten(lager:safe_format("~p ~p", [foo, bar], 1024))), ?assertEqual("FORMAT ERROR: \"~p ~p ~p\" [foo,bar]", lists:flatten(lager:safe_format("~p ~p ~p", [foo, bar], 1024))), ok. +unsafe_format_test() -> + ?assertEqual("foo bar", lists:flatten(lager:unsafe_format("~p ~p", [foo, bar]))), + ?assertEqual("FORMAT ERROR: \"~p ~p ~p\" [foo,bar]", lists:flatten(lager:unsafe_format("~p ~p ~p", [foo, bar]))), + ok. + async_threshold_test_() -> {foreach, fun() -> error_logger:tty(false), + ets:new(async_threshold_test, [set, named_table, public]), + ets:insert_new(async_threshold_test, {sync_toggled, 0}), + ets:insert_new(async_threshold_test, {async_toggled, 0}), application:load(lager), application:set_env(lager, error_logger_redirect, false), application:set_env(lager, async_threshold, 2), @@ -1170,6 +1523,7 @@ application:unset_env(lager, async_threshold), application:stop(lager), application:stop(goldrush), + ets:delete(async_threshold_test), error_logger:tty(true) end, [ @@ -1184,11 +1538,22 @@ %% serialize on mailbox _ = gen_event:which_handlers(lager_event), timer:sleep(500), - %% there should be a ton of outstanding messages now, so async is false - ?assertEqual(false, lager_config:get(async)), - %% wait for all the workers to return, meaning that all the messages have been logged (since we're in sync mode) + + %% By now the flood of messages will have + %% forced the backend throttle to turn off + %% async mode, but it's possible all + %% outstanding requests have been processed, + %% so checking the current status (sync or + %% async) is an exercise in race control. + + %% Instead, we'll see whether the backend + %% throttle has toggled into sync mode at any + %% point in the past + ?assertMatch([{sync_toggled, N}] when N > 0, + ets:lookup(async_threshold_test, sync_toggled)), + %% wait for all the workers to return, meaning that all the messages have been logged (since we're definitely in sync mode at the end of the run) collect_workers(Workers), - %% serialize ont the mailbox again + %% serialize on the mailbox again _ = gen_event:which_handlers(lager_event), %% just in case... timer:sleep(1000), @@ -1268,5 +1633,3 @@ }. -endif. - -
View file
lager-2.1.0.tar.gz/test/pr_nested_record_test.erl -> lager-3.1.0.tar.gz/test/pr_nested_record_test.erl
Changed
@@ -2,13 +2,11 @@ -compile([{parse_transform, lager_transform}]). --record(a, {field1, field2}). --record(b, {field1, field2}). +-record(a, {field1 :: term(), field2 :: term()}). +-record(b, {field1 :: term() , field2 :: term()}). --ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --endif. nested_record_test() -> A = #a{field1 = x, field2 = y},
View file
lager-3.1.0.tar.gz/test/pr_stacktrace_test.erl
Added
@@ -0,0 +1,55 @@ +-module(pr_stacktrace_test). + +-compile([{parse_transform, lager_transform}]). + +-include_lib("eunit/include/eunit.hrl"). + +make_throw() -> + throw({test, exception}). + +bad_arity() -> + lists:concat([], []). + +bad_arg() -> + integer_to_list(1.0). + +pr_stacktrace_throw_test() -> + Result = try + make_throw() + catch + Class:Reason -> + lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) + end, +ExpectedPart = " + pr_stacktrace_test:pr_stacktrace_throw_test/0 line 18 + pr_stacktrace_test:make_throw/0 line 8 +throw:{test,exception}", + ?assertNotEqual(0, string:str(Result, ExpectedPart)). + + +pr_stacktrace_bad_arg_test() -> + Result = try + bad_arg() + catch + Class:Reason -> + lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) + end, +ExpectedPart = " + pr_stacktrace_test:pr_stacktrace_bad_arg_test/0 line 32 + pr_stacktrace_test:bad_arg/0 line 14 +error:badarg", + ?assertNotEqual(0, string:str(Result, ExpectedPart)). + + +pr_stacktrace_bad_arity_test() -> + Result = try + bad_arity() + catch + Class:Reason -> + lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) + end, +ExpectedPart = " + pr_stacktrace_test:pr_stacktrace_bad_arity_test/0 line 46 + lists:concat([], []) +error:undef", + ?assertNotEqual(0, string:str(Result, ExpectedPart)). \ No newline at end of file
View file
lager-2.1.0.tar.gz/test/trunc_io_eqc.erl -> lager-3.1.0.tar.gz/test/trunc_io_eqc.erl
Changed
@@ -91,7 +91,7 @@ %% Generates a printable string gen_print_str() -> - ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~, X < 255]). + ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~, X < 256]). gen_print_bin() -> ?LET(Xs, gen_print_str(), list_to_binary(Xs)).
View file
lager-3.1.0.tar.gz/test/zzzz_gh280_crash.erl
Added
@@ -0,0 +1,33 @@ +%% @doc This test is named zzzz_gh280_crash because it has to be run first and tests are run in +%% reverse alphabetical order. +%% +%% The problem we are attempting to detect here is when log_mf_h is installed as a handler for error_logger +%% and lager starts up to replace the current handlers with its own. This causes a start up crash because +%% OTP error logging modules do not have any notion of a lager-style log level. +-module(zzzz_gh280_crash). +-compile(export_all). + +-include_lib("eunit/include/eunit.hrl"). + +gh280_crash_test() -> + application:stop(lager), + application:stop(goldrush), + + error_logger:tty(false), + %% see https://github.com/erlang/otp/blob/maint/lib/stdlib/src/log_mf_h.erl#L81 + %% for an explanation of the init arguments to log_mf_h + ok = gen_event:add_sup_handler(error_logger, log_mf_h, log_mf_h:init("/tmp", 10000, 5)), + lager:start(), + Result = receive + {gen_event_EXIT,log_mf_h,normal} -> + true; + {gen_event_EXIT,Handler,Reason} -> + {Handler,Reason}; + X -> + X + after 1000 -> + timeout + end, + ?assert(Result), + application:stop(lager), + application:stop(goldrush).
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.