From f4ee8a25051a51b4fdd1318cb6918e05404a466b Mon Sep 17 00:00:00 2001 From: Mickael Remond Date: Wed, 9 Mar 2016 19:12:56 +0100 Subject: [PATCH] Add Elixir Logger Backend to bridge logs from lager We will need to support loglevel bridging. It should help with #966 --- src/ejabberd_logger.erl | 44 ++++++++++++- src/elixir_logger_backend.erl | 115 ++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/elixir_logger_backend.erl diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl index 605b1d633..a13ac814a 100644 --- a/src/ejabberd_logger.erl +++ b/src/ejabberd_logger.erl @@ -102,6 +102,29 @@ get_string_env(Name, Default) -> %% @spec () -> ok start() -> + StartedApps = application:which_applications(5000), + case lists:keyfind(logger, 1, StartedApps) of + {logger, _, _} -> + error_logger:info_msg("Ignoring logger options, using Elixir Logger.", []), + %% Do not start lager, we rely on Elixir Logger + do_start_for_logger(); + _ -> + do_start() + end. + +do_start_for_logger() -> + application:load(sasl), + application:set_env(sasl, sasl_error_logger, false), + application:load(lager), + application:set_env(lager, error_logger_redirect, false), + application:set_env(lager, error_logger_whitelist, ['Elixir.Logger.ErrorHandler']), + application:set_env(lager, crash_log, false), + application:set_env(lager, handlers, [{elixir_logger_backend, [{level, debug}]}]), + ejabberd:start_app(lager), + ok. + +%% Start lager +do_start() -> application:load(sasl), application:set_env(sasl, sasl_error_logger, false), application:load(lager), @@ -145,7 +168,7 @@ rotate_log() -> %% @spec () -> {loglevel(), atom(), string()} get() -> - case lager:get_loglevel(lager_console_backend) of + case get_lager_loglevel() of none -> {0, no_log, "No log"}; emergency -> {1, critical, "Critical"}; alert -> {1, critical, "Critical"}; @@ -168,7 +191,7 @@ set(LogLevel) when is_integer(LogLevel) -> 5 -> debug; E -> throw({wrong_loglevel, E}) end, - case lager:get_loglevel(lager_console_backend) of + case get_lager_loglevel() of LagerLogLevel -> ok; _ -> @@ -186,3 +209,20 @@ set(LogLevel) when is_integer(LogLevel) -> set({_LogLevel, _}) -> error_logger:error_msg("custom loglevels are not supported for 'lager'"), {module, lager}. + +get_lager_loglevel() -> + R = case get_lager_handlers() of + [] -> none; + [elixir_logger_backend] -> debug; + [FirstHandler|_] -> + lager:get_loglevel(FirstHandler) + end, + R. + +get_lager_handlers() -> + case catch gen_event:which_handlers(lager_event) of + {'EXIT',noproc} -> + []; + Result -> + Result + end. diff --git a/src/elixir_logger_backend.erl b/src/elixir_logger_backend.erl new file mode 100644 index 000000000..6bd5b638e --- /dev/null +++ b/src/elixir_logger_backend.erl @@ -0,0 +1,115 @@ +%%%------------------------------------------------------------------- +%%% @author Mickael Remond +%%% @doc +%%% This module bridges lager logs to Elixir Logger. +%%% @end +%%% Created : 9 March 2016 by Mickael Remond +%%% +%%% ejabberd, Copyright (C) 2002-2016 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%------------------------------------------------------------------- + +-module(elixir_logger_backend). + +-behaviour(gen_event). + +-export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, + code_change/3]). + +init(_Opts) -> + State = [], + {ok, State}. + +%% @private +handle_event({log, LagerMsg}, State) -> + #{mode := Mode, truncate := Truncate, level := MinLevel, utc_log := UTCLog} = 'Elixir.Logger.Config':'__data__'(), + MsgLevel = severity_to_level(lager_msg:severity(LagerMsg)), + case {lager_util:is_loggable(LagerMsg, debug, ?MODULE), 'Elixir.Logger':compare_levels(MsgLevel, MinLevel)} of + {_, lt}-> + {ok, State}; + {true, _} -> + Metadata = normalize_pid(lager_msg:metadata(LagerMsg)), + Message = 'Elixir.Logger.Utils':truncate(lager_msg:message(LagerMsg), Truncate), + Timestamp = timestamp(lager_msg:timestamp(LagerMsg), UTCLog), + GroupLeader = case proplists:get_value(pid, Metadata, self()) of + Pid when is_pid(Pid) -> + erlang:process_info(self(), group_leader); + _ -> {group_leader, self()} + end, + notify(Mode, {MsgLevel, GroupLeader, {'Elixir.Logger', Message, Timestamp, Metadata}}), + {ok, State}; + _ -> + {ok, State} + end; +handle_event(_, State) -> + {ok, State}. + +%% @private +%% TODO Handle loglevels +handle_call(_Msg, State) -> + {ok, ok, State}. + +%% @private +handle_info(_Msg, State) -> + {ok, State}. + +%% @private +terminate(_Reason, _State) -> + ok. + +%% @private +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +notify(sync, Msg) -> + gen_event:sync_notify('Elixir.Logger', Msg); +notify(async, Msg) -> gen_event:notify('Elixir.Logger', Msg). + +normalize_pid(Metadata) -> + case proplists:get_value(pid, Metadata) of + Pid when is_pid(Pid) -> Metadata; + Pid when is_list(Pid) -> + M1 = proplists:delete(pid, Metadata), + case catch erlang:list_to_pid(Pid) of + {'EXIT', _} -> + M1; + PidAsPid -> + [{pid, PidAsPid}|M1] + end; + _ -> + proplists:delete(pid, Metadata) + end. + +%% Return timestamp with milliseconds +timestamp(Time, UTCLog) -> + {_, _, Micro} = erlang:timestamp(), + {Date, {Hours, Minutes, Seconds}} = + case UTCLog of + true -> calendar:now_to_universal_time(Time); + false -> calendar:now_to_local_time(Time) + end, + {Date, {Hours, Minutes, Seconds, Micro div 1000}}. + + +severity_to_level(debug) -> debug; +severity_to_level(info) -> info; +severity_to_level(notice) -> info; +severity_to_level(warning) -> warn; +severity_to_level(error) -> error; +severity_to_level(critical) -> error; +severity_to_level(alert) -> error; +severity_to_level(emergency) -> error.