Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable lager to use the OTP logger's event pipeline #524

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e1c4ed1
Add a `lager_use_logger` parse transform to rewrite lager calls to lo…
Vagabond Feb 3, 2019
f1c7e3a
Couple fixes
Vagabond Feb 3, 2019
7aaad5c
Handle log messages with no format arguments
Vagabond Feb 3, 2019
a68e456
Backwards
Vagabond Feb 3, 2019
ad25bf8
Add an env var to stop lager actually booting
Vagabond Feb 3, 2019
62d95d2
Add a module to convert logger formatters to lager formatters
Vagabond Feb 3, 2019
0970513
Clear colors at end of line
Vagabond Feb 3, 2019
e53ecb0
Typo
Vagabond Feb 3, 2019
8a2cf8d
Add some formatters for the standard OTP reports
Vagabond Feb 3, 2019
0176941
Fallback for unhandled reports
Vagabond Feb 3, 2019
c793c03
Tweak
Vagabond Feb 3, 2019
9b8ee23
Honor supervisor message suppression config
Vagabond Feb 3, 2019
4f8d245
Feh
Vagabond Feb 3, 2019
9ed7ee8
Add supervisor error reports
Vagabond Feb 4, 2019
8ac924b
Debugging
Vagabond Feb 4, 2019
7e247dc
Typo:
Vagabond Feb 4, 2019
0ba40a2
Add application start
Vagabond Feb 4, 2019
24651de
Use the right key names
Vagabond Feb 4, 2019
0e07296
Add application stops
Vagabond Feb 4, 2019
5667c95
Try to switch to using a report_cb
Vagabond Feb 4, 2019
7b8d2c5
More fighting with report_cb
Vagabond Feb 4, 2019
ce11e41
Use report_cb from config or from metadata
Vagabond Feb 4, 2019
7af01cc
Try to supress log messages better
Vagabond Feb 9, 2019
566d0f3
Fix
Vagabond Feb 9, 2019
60f6b38
Typo
Vagabond Feb 9, 2019
2259907
Fix
Vagabond Feb 9, 2019
3d38191
Fix
Vagabond Feb 9, 2019
3c275e4
Use better function
Vagabond Feb 24, 2019
0803df0
How the heck did this slip in?
Vagabond Mar 1, 2020
0dff911
Add some docs
Vagabond Mar 2, 2020
c62732d
WIP function to turn lager config into logger config
Vagabond Mar 2, 2020
6746643
Rework logger configuration and add logger config generator
Vagabond Mar 2, 2020
244463e
logger_std_h supports file rotation better than disk_log, so use that
Vagabond Mar 2, 2020
1121d28
Handle columns in transform
Vagabond Oct 2, 2024
20ee759
Attempt to handle modern wacky crash reports better
Vagabond Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,69 @@ Example Usage:
:lager.warning('Some message with a term: ~p', [term])
```

Integration with OTP's Logger
-----------------------------

Now that OTP includes a modern log event pipeline, the Lager project has decided
to work on winding down Lager development in favor of unifying the Erlang
ecosystem behind Logger. To that end, Lager now supports being configured to use
its parse transform to rewrite lager calls into logger calls. To enable this
mode, the following changes are required:

In sys.config:
```
[
{kernel,
[{logger,
[{handler, default, logger_std_h,
#{formatter => {lager_logger_formatter, #{report_cb => fun lager_logger_formatter:report_cb/1}}}},
]}]},
...
{lager, [
{lager_use_logger, true},
...
]}
]
```

The logger stanza changes are not needed if you wish to use logger's default
formatter.

In your applications top level rebar.config (probably requires rebar3):

```
{overrides, [{add, [{erl_opts, [{lager_use_logger, true}]}]}]}.
```

This will force the parse transform configuration to apply to all the
dependencies as well. Make sure you've defined the lager version in your
toplevel application's rebar.config and that the version is high enough to
support these options. A toplevel dependency will override any lager
dependencies in any of your application's dependencies and thus ensure the parse
transform is the right version.

To generate a logger configuration from your lager configuration you can do:

```
lager:generate_logger_configuration()
```

If you wish to simply use your existing lager configuration as is, and have
lager configure logger you can, in your sys.config in the lager stanza:

```
{lager, [
{configure_logger, true},
...
]}
```

Alternatively you can use:

```
lager:configure_logger()
```

3.x Changelog
-------------
3.6.8 - 21 December 2018
Expand Down
62 changes: 50 additions & 12 deletions src/error_logger_lager_h.erl
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ log_event(Event, #state{sink=Sink} = State) ->
case {FormatRaw, Fmt} of
{false, "** Generic server "++_} ->
%% gen_server terminate
?CRASH_LOG(Event),
{Reason, Name} = case Args of
[N, _Msg, _State, R] ->
{R, N};
Expand All @@ -196,31 +197,68 @@ log_event(Event, #state{sink=Sink} = State) ->
%% TODO do something with them
{R, N}
end,
?CRASH_LOG(Event),
{Md, Formatted} = format_reason_md(Reason),
?LOGFMT(Sink, error, [{pid, Pid}, {name, Name} | Md], "gen_server ~w terminated with reason: ~s",
[Name, Formatted]);
{false, "** State machine "++_} ->
?CRASH_LOG(Event),
%% Check if the terminated process is gen_fsm or gen_statem
%% since they generate the same exit message
{Type, Name, StateName, Reason} = case Args of
[TName, _Msg, TStateName, _StateData, TReason] ->
{gen_fsm, TName, TStateName, TReason};
[TName, _Msg, {TStateName, _StateData}, _ExitType, TReason, _FsmType, Stacktrace] ->
{gen_statem, TName, TStateName, {TReason, Stacktrace}};
[TName, _Msg, [{TStateName, _StateData}], _ExitType, TReason, _FsmType, Stacktrace] ->
%% sometimes gen_statem wraps its statename/data in a list for some reason???
{gen_statem, TName, TStateName, {TReason, Stacktrace}}
end,

%% gen_statem has a variable set of fields it will include
%% so we have to parse the format string to find the ones we want
%%
%% first, get rid of any newline formatters
Fm2 = lists:flatten(string:replace(Fmt, "~n", "", all)),
Tokens = string:tokens(Fm2, "~"),


{FormatArgs, _} = lists:foldl(fun(Token, {Acc, [H|T]}) ->
case string:tokens(Token, "**") of
[" State machine "] ->
{maps:put(name, H, Acc), T};
[_ ," Last event = "] ->
{maps:put(last_event, H, Acc), T};
[_, " When server state = "] ->
{maps:put(state, H, Acc), T};
[_, " Reason for termination = "] ->
{maps:put(reason, H, Acc), T};
[_, " Callback modules = "] ->
{maps:put(callback_modules, H, Acc), T};
[_, " Callback mode = "] ->
{maps:put(callback_mode, H, Acc), T};
[_, " Stacktrace =" |_] ->
{maps:put(stacktrace, H, Acc), T};
[_, " Time-outs: " |_] ->
{maps:put(time_outs, H, Acc), T};
Unknown ->
{Acc, T}
end;
(_Token, {Acc, []}) ->
{Acc, []}
end, {maps:new(), Args}, Tokens),

%%{Type, Name, StateName, Reason} = case Args of
%% [TName, _Msg, TStateName, _StateData, TReason] ->
%% {gen_fsm, TName, TStateName, TReason};
%% [TName, _Msg, {TStateName, _StateData}, _ExitType, TReason, _FsmType, Stacktrace] ->
%% {gen_statem, TName, TStateName, {TReason, Stacktrace}};
%% [TName, _Msg, [{TStateName, _StateData}], _ExitType, TReason, _FsmType, Stacktrace] ->
%% %% sometimes gen_statem wraps its statename/data in a list for some reason???
%% {gen_statem, TName, TStateName, {TReason, Stacktrace}}
%%end,
Type = 'state machine',
Name = maps:get(name, FormatArgs),
{StateName, _StateData} = maps:get(state, FormatArgs),
Reason = maps:get(reason, FormatArgs),
{Md, Formatted} = format_reason_md(Reason),
?CRASH_LOG(Event),
?LOGFMT(Sink, error, [{pid, Pid}, {name, Name} | Md], "~s ~w in state ~w terminated with reason: ~s",
[Type, Name, StateName, Formatted]);
{false, "** gen_event handler"++_} ->
%% gen_event handler terminate
?CRASH_LOG(Event),
[ID, Name, _Msg, _State, Reason] = Args,
{Md, Formatted} = format_reason_md(Reason),
?CRASH_LOG(Event),
?LOGFMT(Sink, error, [{pid, Pid}, {name, Name} | Md], "gen_event ~w installed in ~w terminated with reason: ~s",
[ID, Name, Formatted]);
{false, "** Cowboy handler"++_} ->
Expand Down
62 changes: 61 additions & 1 deletion src/lager.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
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]).
do_log/9, do_log/10, do_log_unsafe/10, pr/2, pr/3, pr_stacktrace/1, pr_stacktrace/2,
generate_logger_config/0, configure_logger/0]).

-type log_level() :: none | debug | info | notice | warning | error | critical | alert | emergency.
-type log_level_number() :: 0..7.
Expand Down Expand Up @@ -689,6 +690,65 @@ rotate_handler(Handler) ->
rotate_handler(Handler, Sink) ->
gen_event:call(Sink, Handler, rotate, ?ROTATE_TIMEOUT).

generate_logger_config() ->
Handlers = application:get_env(lager, handlers, lager_app:default_handlers()),
{Level, NewHandlers} = generate_logger_handlers(Handlers, {notice, []}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of "translating" Lager handlers (which will not support custom handlers anyway) to logger handlers, add new handler that will forward all messages from the logger to lager's gen_event as a compatibility layer.

{kernel, [{logger_level, Level}, {logger, NewHandlers}]}.

configure_logger() ->
Handlers = application:get_env(lager, handlers, lager_app:default_handlers()),
WhitelistedLoggerHandlers = application:get_env(lager, whitelisted_logger_handlers, []),
[ ok = logger:remove_handler(Id) || #{id := Id} <- logger:get_handler_config(), not lists:member(Id, WhitelistedLoggerHandlers) ],
{Level, NewHandlers} = generate_logger_handlers(Handlers, {notice, []}),
logger:set_primary_config(maps:merge(logger:get_primary_config(), #{level => Level})),
[ ok = logger:add_handler(HandlerId, HandlerModule, HandlerConfig) || {handler, HandlerId, HandlerModule, HandlerConfig} <- NewHandlers ],
ok.

generate_logger_handlers([], Acc) ->
Acc;
generate_logger_handlers([{lager_console_backend, Config}|Tail], {CurrentLevel, Acc}) ->
Level = proplists:get_value(level, Config, info),
Formatter = proplists:get_value(formatter, Config, lager_default_formatter),
FormatterConfig = proplists:get_value(formatter_config, Config, []),
Handler = {handler, console, logger_std_h, #{level => Level, formatter =>
{lager_logger_formatter, #{report_cb => fun lager_logger_formatter:report_cb/1,
formatter => Formatter,
formatter_config => FormatterConfig}}}},
NewLevel = case lager_util:level_to_num(Level) > lager_util:level_to_num(CurrentLevel) of
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of that you can use logger:compare_levels/2.

true ->
Level;
false ->
CurrentLevel
end,
generate_logger_handlers(Tail, {NewLevel, [Handler|Acc]});
generate_logger_handlers([{lager_file_backend, Config}|Tail], {CurrentLevel, Acc}) ->
Level = proplists:get_value(level, Config, info),
File = proplists:get_value(file, Config),
LogRoot = application:get_env(lager, log_root, ""),
Size = proplists:get_value(size, Config, 10485760),
Count = proplists:get_value(count, Config, 5),
Formatter = proplists:get_value(formatter, Config, lager_default_formatter),
FormatterConfig = proplists:get_value(formatter_config, Config, []),
%% the standard log handler has a file mode with size based rotation support that is much saner than
%% disk_log's, so use that here
Handler = {handler, list_to_atom(File), logger_std_h, #{level => Level,
config => #{type => file,
file => filename:join(LogRoot, File),
max_no_files => Count,
max_no_bytes => Size},
formatter =>
{lager_logger_formatter, #{report_cb => fun lager_logger_formatter:report_cb/1,
formatter => Formatter,
formatter_config => FormatterConfig}}}},
NewLevel = case lager_util:level_to_num(Level) > lager_util:level_to_num(CurrentLevel) of
true ->
Level;
false ->
CurrentLevel
end,
generate_logger_handlers(Tail, {NewLevel, [Handler|Acc]}).


%% @private
trace_func(#trace_func_state_v1{pid=Pid, level=Level, format_string=Fmt}=FuncState, Event, ProcState) ->
lager:log(Level, Pid, Fmt, [Event, ProcState]),
Expand Down
27 changes: 21 additions & 6 deletions src/lager_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
start_handler/3,
configure_sink/2,
stop/1,
boot/1]).
boot/1,
default_handlers/0]).

%% The `application:get_env/3` compatibility wrapper was useful
%% for other modules in r15 and before
Expand Down Expand Up @@ -226,11 +227,22 @@ get_env(Application, Key, Default) ->

start(_StartType, _StartArgs) ->
{ok, Pid} = lager_sup:start_link(),
SavedHandlers = boot(),
_ = boot('__all_extra'),
_ = boot('__traces'),
clean_up_config_checks(),
{ok, Pid, SavedHandlers}.
case application:get_env(lager, lager_use_logger, false) of
false ->
SavedHandlers = boot(),
_ = boot('__all_extra'),
_ = boot('__traces'),
clean_up_config_checks(),
{ok, Pid, SavedHandlers};
true ->
case application:get_env(lager, configure_logger, false) of
true ->
ok = lager:configure_logger();
false ->
ok
end,
{ok, Pid}
end.

boot() ->
%% Handle the default sink.
Expand Down Expand Up @@ -276,6 +288,9 @@ stop(Handlers) ->
error_logger:add_report_handler(Handler)
end, Handlers).

default_handlers() ->
?DEFAULT_HANDLER_CONF.

expand_handlers([]) ->
[];
expand_handlers([{lager_file_backend, [{Key, _Value}|_]=Config}|T]) when is_atom(Key) ->
Expand Down
Loading