%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*- % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod: %%% %%%------------------------------------------------------------------------ %%% @doc %%% ==CloudI Logger== %%% @end %%% %%% BSD LICENSE %%% %%% Copyright (c) 2009-2017, Michael Truog %%% All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions are met: %%% %%% * Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% * Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in %%% the documentation and/or other materials provided with the %%% distribution. %%% * All advertising materials mentioning features or use of this %%% software must display the following acknowledgment: %%% This product includes software developed by Michael Truog %%% * The name of the author may not be used to endorse or promote %%% products derived from this software without specific prior %%% written permission %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND %%% CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES %%% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE %%% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, %%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, %%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS %%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING %%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE %%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH %%% DAMAGE. %%% %%% @author Michael Truog %%% @copyright 2009-2017 Michael Truog %%% @version 1.6.1 {@date} {@time} %%%------------------------------------------------------------------------ -module(cloudi_core_i_logger). -author('mjtruog [at] gmail (dot) com'). -behaviour(gen_server). %% external interface -export([start_link/1, current_function/0, set/1, file_set/1, stdout_set/1, level_set/1, syslog_set/1, formatters_set/1, redirect_set/1, redirect_update/1, fatal/8, error/8, warn/8, info/8, debug/8, trace/8, metadata_get/0, metadata_set/1, format/2, format/3]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include("cloudi_core_i_configuration.hrl"). -include("cloudi_core_i_constants.hrl"). -include_lib("kernel/include/file.hrl"). %% logging macros used only within this module -define(LOG_INFO_T0(Format, Args, State), log_message_internal_t0(info, ?LINE, ?FUNCTION_NAME, ?FUNCTION_ARITY, Format, Args, State)). -define(LOG_INFO_T1(Format, Args, State), log_message_internal_t1(info, ?LINE, ?FUNCTION_NAME, ?FUNCTION_ARITY, Format, Args, State)). -record(state, { file_path = undefined :: undefined | string(), interface_module = undefined :: undefined | binary(), fd = undefined :: undefined | file:fd(), inode = undefined :: undefined | non_neg_integer(), stdout = undefined :: undefined | port(), main_level = undefined % both file and stdout level :: undefined | cloudi_service_api:loglevel(), level = undefined :: undefined | cloudi_service_api:loglevel(), mode = async :: async | sync, destination = undefined :: undefined | ?MODULE | {?MODULE, node()}, syslog = undefined :: undefined | pid(), syslog_level = undefined :: undefined | cloudi_service_api:loglevel(), formatters :: undefined | #config_logging_formatters{}, formatters_level :: undefined | cloudi_service_api:loglevel(), aspects_log_before :: list(cloudi_service_api:aspect_log_before()), aspects_log_after :: list(cloudi_service_api:aspect_log_after()), logger_node :: node(), logger_self :: pid() }). %%%------------------------------------------------------------------------ %%% External interface functions %%%------------------------------------------------------------------------ %%------------------------------------------------------------------------- %% @doc %% ===Start the logging server with the supplied configuration.=== %% @end %%------------------------------------------------------------------------- -spec start_link(#config{}) -> {'ok', pid()} | {'error', any()}. start_link(#config{logging = LoggingConfig}) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [LoggingConfig], []). %%------------------------------------------------------------------------- %% @doc %% ===Get the current function name being executed.=== %% @end %%------------------------------------------------------------------------- -spec current_function() -> atom(). current_function() -> catch throw(x), [_, {_, F, _, _} | _] = erlang:get_stacktrace(), F. %%------------------------------------------------------------------------- %% @doc %% ===Change the logging configuration.=== %% @end %%------------------------------------------------------------------------- -spec set(LoggingConfig :: #config_logging{}) -> 'ok' | {'error', file:posix() | badarg | system_limit}. set(#config_logging{file = FilePath} = LoggingConfig) -> if FilePath =:= undefined -> gen_server:cast(?MODULE, {set, LoggingConfig}); is_list(FilePath) -> case file:open(FilePath, [raw, append]) of {ok, Fd} -> file:close(Fd), gen_server:cast(?MODULE, {set, LoggingConfig}); {error, _} = Error -> Error end end. %%------------------------------------------------------------------------- %% @doc %% ===Change the file output path.=== %% @end %%------------------------------------------------------------------------- -spec file_set(FilePath :: string() | undefined) -> 'ok' | {'error', file:posix() | badarg | system_limit}. file_set(undefined) -> gen_server:cast(?MODULE, {file_set, undefined}); file_set(FilePath) when is_list(FilePath) andalso is_integer(hd(FilePath)) -> case file:open(FilePath, [raw, append]) of {ok, Fd} -> file:close(Fd), gen_server:cast(?MODULE, {file_set, FilePath}); {error, _} = Error -> Error end. %%------------------------------------------------------------------------- %% @doc %% ===Change the stdout output state.=== %% @end %%------------------------------------------------------------------------- -spec stdout_set(Stdout :: boolean()) -> 'ok'. stdout_set(Stdout) when is_boolean(Stdout) -> gen_server:cast(?MODULE, {stdout_set, Stdout}). %%------------------------------------------------------------------------- %% @doc %% ===Change the file output log level.=== %% @end %%------------------------------------------------------------------------- -spec level_set(Level :: cloudi_service_api:loglevel() | undefined) -> 'ok'. level_set(undefined) -> gen_server:cast(?MODULE, {level_set, off}); level_set(Level) when Level =:= fatal; Level =:= error; Level =:= warn; Level =:= info; Level =:= debug; Level =:= trace; Level =:= off -> gen_server:cast(?MODULE, {level_set, Level}). %%------------------------------------------------------------------------- %% @doc %% ===Change the syslog configuration.=== %% @end %%------------------------------------------------------------------------- -spec syslog_set(SyslogConfig :: #config_logging_syslog{} | undefined) -> 'ok'. syslog_set(SyslogConfig) when SyslogConfig =:= undefined; is_record(SyslogConfig, config_logging_syslog) -> gen_server:cast(?MODULE, {syslog_set, SyslogConfig}). %%------------------------------------------------------------------------- %% @doc %% ===Change the logging formatters.=== %% @end %%------------------------------------------------------------------------- -spec formatters_set(FormattersConfig :: #config_logging_formatters{} | undefined) -> 'ok'. formatters_set(FormattersConfig) when FormattersConfig =:= undefined; is_record(FormattersConfig, config_logging_formatters) -> gen_server:cast(?MODULE, {formatters_set, FormattersConfig}). %%------------------------------------------------------------------------- %% @doc %% ===Redirect this node's logging to a different node.=== %% @end %%------------------------------------------------------------------------- -spec redirect_set(Node :: atom()) -> 'ok'. redirect_set(Node) -> cloudi_core_i_nodes:logging_redirect_set(Node). %%------------------------------------------------------------------------- %% @doc %% ===Update the destination of logging output.=== %% @end %%------------------------------------------------------------------------- -spec redirect_update(Node :: atom()) -> 'ok'. redirect_update(Node) -> gen_server:cast(?MODULE, {redirect_update, Node}). %%------------------------------------------------------------------------- %% @doc %% ===Critical log message.=== %% which indicates the system has failed and can not continue. %% Called with ?LOG_FATAL(Format, []). %% @end %%------------------------------------------------------------------------- -spec fatal(Mode :: async | sync, Process :: atom() | {atom(), node()}, Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: arity() | undefined, Format :: string(), Args :: list() | undefined) -> 'ok'. fatal(Mode, Process, Module, Line, Function, Arity, Format, Args) -> log_message_external(Mode, Process, fatal, Module, Line, Function, Arity, Format, Args). %%------------------------------------------------------------------------- %% @doc %% ===Error log message.=== %% which indicates a subsystem has failed but the failure is not fatal. %% Called with ?LOG_ERROR(Format, []). %% @end %%------------------------------------------------------------------------- -spec error(Mode :: async | sync, Process :: atom() | {atom(), node()}, Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: arity() | undefined, Format :: string(), Args :: list() | undefined) -> 'ok'. error(Mode, Process, Module, Line, Function, Arity, Format, Args) -> log_message_external(Mode, Process, error, Module, Line, Function, Arity, Format, Args). %%------------------------------------------------------------------------- %% @doc %% ===Warning log message.=== %% which indicates an unexpected occurance was found in a subsystem. %% Called with ?LOG_WARN(Format, []). %% @end %%------------------------------------------------------------------------- -spec warn(Mode :: async | sync, Process :: atom() | {atom(), node()}, Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: arity() | undefined, Format :: string(), Args :: list() | undefined) -> 'ok'. warn(Mode, Process, Module, Line, Function, Arity, Format, Args) -> log_message_external(Mode, Process, warn, Module, Line, Function, Arity, Format, Args). %%------------------------------------------------------------------------- %% @doc %% ===Info log message.=== %% which indicates a subsystem has changed state. %% Called with ?LOG_INFO(Format, []). %% @end %%------------------------------------------------------------------------- -spec info(Mode :: async | sync, Process :: atom() | {atom(), node()}, Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: arity() | undefined, Format :: string(), Args :: list() | undefined) -> 'ok'. info(Mode, Process, Module, Line, Function, Arity, Format, Args) -> log_message_external(Mode, Process, info, Module, Line, Function, Arity, Format, Args). %%------------------------------------------------------------------------- %% @doc %% ===Debug log message.=== %% which reports subsystem data that should be useful for debugging. %% Called with ?LOG_DEBUG(Format, []). %% @end %%------------------------------------------------------------------------- -spec debug(Mode :: async | sync, Process :: atom() | {atom(), node()}, Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: arity() | undefined, Format :: string(), Args :: list() | undefined) -> 'ok'. debug(Mode, Process, Module, Line, Function, Arity, Format, Args) -> log_message_external(Mode, Process, debug, Module, Line, Function, Arity, Format, Args). %%------------------------------------------------------------------------- %% @doc %% ===Trace log message.=== %% which reports subsystem data that is only for tracing execution. %% Called with ?LOG_TRACE(Format, []). %% @end %%------------------------------------------------------------------------- -spec trace(Mode :: async | sync, Process :: atom() | {atom(), node()}, Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: arity() | undefined, Format :: string(), Args :: list() | undefined) -> 'ok'. trace(Mode, Process, Module, Line, Function, Arity, Format, Args) -> log_message_external(Mode, Process, trace, Module, Line, Function, Arity, Format, Args). %%------------------------------------------------------------------------- %% @doc %% ===Get lager-compatible metadata.=== %% @end %%------------------------------------------------------------------------- -spec metadata_get() -> list({atom(), any()}). metadata_get() -> lager_metadata_get(). %%------------------------------------------------------------------------- %% @doc %% ===Set lager-compatible metadata.=== %% @end %%------------------------------------------------------------------------- -spec metadata_set(list({atom(), any()})) -> ok. metadata_set(L) -> lager_metadata_set(L). %%------------------------------------------------------------------------- %% @doc %% ===Lager formatter for legacy output.=== %% Provides legacy CloudI logger output formatting in a %% lager formatter function. %% @end %%------------------------------------------------------------------------- format(Msg, Config) -> format(Msg, Config, []). %%------------------------------------------------------------------------- %% @doc %% ===Lager formatter example with default output.=== %% @end %%------------------------------------------------------------------------- -spec format(Msg :: {lager_msg, Destinations :: list(), Metadata :: list({atom(), any()}), Severity :: debug | emergency | error | info | warning, Datetime :: {string(), string()}, Timestamp :: erlang:timestamp(), Message :: list()}, Config :: list(), Colors :: any()) -> iodata(). format(Msg, _Config, _) -> {lager_msg, _Destinations, MetaData, Severity, _DateTime, Timestamp, Message} = Msg, Level = lager_severity_input(Severity), Defaults = [{function, undefined}, {module, undefined}, {line, undefined}, {node, undefined}, {pid, undefined}], [Function, Module, Line, Node, PidStr | ExtraMetaData] = cloudi_proplists:take_values(Defaults, MetaData), LogMessage = Message, format_line(Level, Timestamp, Node, PidStr, Module, Line, Function, undefined, ExtraMetaData, LogMessage). %%%------------------------------------------------------------------------ %%% Callback functions from gen_server %%%------------------------------------------------------------------------ init([#config_logging{file = FilePath, stdout = Stdout, level = MainLevel, redirect = NodeLogger, syslog = SyslogConfig, formatters = FormattersConfig, aspects_log_before = AspectsLogBefore, aspects_log_after = AspectsLogAfter}]) -> StdoutPort = stdout_open(Stdout), FormattersLevel = case FormattersConfig of undefined -> undefined; #config_logging_formatters{level = FormattersLevel0} -> FormattersLevel0 end, #state{mode = Mode} = State = #state{stdout = StdoutPort, main_level = MainLevel, formatters = FormattersConfig, formatters_level = FormattersLevel, aspects_log_before = AspectsLogBefore, aspects_log_after = AspectsLogAfter, logger_node = node(), logger_self = self()}, #state{syslog_level = SyslogLevel} = StateNext = case SyslogConfig of undefined -> State; #config_logging_syslog{level = SyslogLevel0} -> State#state{syslog = syslog_open(SyslogConfig), syslog_level = SyslogLevel0} end, Level = log_level([MainLevel, SyslogLevel, FormattersLevel]), Destination = if NodeLogger == node(); NodeLogger =:= undefined -> ?MODULE; true -> {?MODULE, NodeLogger} end, case load_interface_module(Level, Mode, Destination) of {ok, Binary} when Destination == ?MODULE -> case log_file_open(FilePath, StateNext#state{interface_module = Binary, level = Level, destination = Destination}) of {ok, _} = Success -> Success; {error, Reason} -> {stop, Reason} end; {ok, Binary} -> case ?LOG_INFO_T0("redirecting log output to ~ts", [NodeLogger], StateNext#state{file_path = FilePath, interface_module = Binary, level = Level, destination = ?MODULE}) of {ok, StateNew} -> {ok, StateNew#state{destination = Destination}}; {{error, Reason}, _} -> {stop, Reason} end; {error, Reason} -> {stop, Reason} end. handle_call({Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage}, _, State) -> case log_message_internal(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, State) of {ok, StateNext} -> case log_mode_check(StateNext) of {ok, StateNew} -> {reply, ok, StateNew}; {error, Reason} -> {stop, Reason, ok, StateNext} end; {{error, Reason}, StateNext} -> {stop, Reason, ok, StateNext} end; handle_call(Request, _, State) -> {stop, cloudi_string:format("Unknown call \"~p\"~n", [Request]), error, State}. handle_cast({set, LoggingConfig}, State) -> case log_config_set(LoggingConfig, State) of {ok, NextState} -> case log_level_update(NextState) of {ok, NewState} -> {noreply, NewState}; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; handle_cast({file_set, FilePath}, State) -> case log_config_file_set(FilePath, State) of {ok, NewState} -> {noreply, NewState}; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; handle_cast({stdout_set, Stdout}, State) -> {ok, NewState} = log_config_stdout_set(Stdout, State), {noreply, NewState}; handle_cast({level_set, MainLevel}, State) -> case log_config_main_level_set(MainLevel, State) of {ok, NextState} -> case log_level_update(NextState) of {ok, NewState} -> {noreply, NewState}; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; handle_cast({syslog_set, SyslogConfig}, State) -> case log_config_syslog_set(SyslogConfig, State) of {ok, NextState} -> case log_level_update(NextState) of {ok, NewState} -> {noreply, NewState}; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; handle_cast({formatters_set, FormattersConfigNew}, State) -> case log_config_formatters_set(FormattersConfigNew, State) of {ok, NextState} -> case log_level_update(NextState) of {ok, NewState} -> {noreply, NewState}; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; {{error, Reason}, NewState} -> {stop, Reason, NewState} end; handle_cast({redirect_update, Node}, State) -> Destination = if Node == node(); Node =:= undefined -> ?MODULE; true -> {?MODULE, Node} end, case log_redirect(Node, Destination, State) of {ok, StateNew} -> {noreply, StateNew}; {error, Reason, StateNew} -> {stop, Reason, StateNew} end; handle_cast({Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage}, State) -> case log_message_internal(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, State) of {ok, StateNext} -> case log_mode_check(StateNext) of {ok, StateNew} -> {noreply, StateNew}; {error, Reason} -> {stop, Reason, StateNext} end; {{error, Reason}, StateNext} -> {stop, Reason, StateNext} end; handle_cast(Request, State) -> {stop, cloudi_string:format("Unknown cast \"~p\"~n", [Request]), State}. handle_info(Request, State) -> {stop, cloudi_string:format("Unknown info \"~p\"~n", [Request]), State}. terminate(_, #state{fd = Fd, stdout = StdoutPort, syslog = Syslog}) -> _ = (catch file:close(Fd)), ok = stdout_close(StdoutPort), ok = syslog_close(Syslog), ok. code_change(_, State, _) -> {ok, State}. %%%------------------------------------------------------------------------ %%% Private functions %%%------------------------------------------------------------------------ log_config_set(#config_logging{file = FilePath, stdout = Stdout, level = MainLevel, redirect = NodeLogger, syslog = SyslogConfig, formatters = FormattersConfig, aspects_log_before = AspectsLogBefore, aspects_log_after = AspectsLogAfter}, State) -> case eval([{MainLevel, fun log_config_main_level_set/2}, {FilePath, fun log_config_file_set/2}, {Stdout, fun log_config_stdout_set/2}, {SyslogConfig, fun log_config_syslog_set/2}, {FormattersConfig, fun log_config_formatters_set/2}], State#state{aspects_log_before = AspectsLogBefore, aspects_log_after = AspectsLogAfter}) of {ok, _} = Success -> ok = cloudi_core_i_nodes:logging_redirect_set(NodeLogger), Success; {{error, _}, _} = ErrorResult -> ErrorResult end. log_config_main_level_set(MainLevel, #state{main_level = MainLevel} = State) -> {ok, State}; log_config_main_level_set(MainLevelNew, #state{main_level = MainLevelOld} = State) -> case ?LOG_INFO_T0("changing main loglevel from ~s to ~s", [MainLevelOld, MainLevelNew], State) of {ok, StateNew} -> {ok, StateNew#state{main_level = MainLevelNew}}; {{error, _}, _} = ErrorResult -> ErrorResult end. log_config_file_set(FilePath, #state{file_path = FilePath} = State) -> {ok, State}; log_config_file_set(FilePathNew, #state{file_path = FilePathOld} = State) -> FilePathNewStr = if is_list(FilePathNew) -> io_lib:format("\"~ts\"", [FilePathNew]); FilePathNew =:= undefined -> "'undefined'" end, FilePathOldStr = if is_list(FilePathOld) -> io_lib:format("\"~ts\"", [FilePathOld]); FilePathOld =:= undefined -> "'undefined'" end, case ?LOG_INFO_T0("changing file path from ~s to ~s", [FilePathOldStr, FilePathNewStr], State) of {ok, #state{fd = FdOld} = StateNew} -> file:close(FdOld), {ok, StateNew#state{file_path = FilePathNew, fd = undefined, inode = undefined}}; {{error, _}, _} = ErrorResult -> ErrorResult end. log_config_stdout_set(Stdout, #state{stdout = StdoutPort} = State) when Stdout =:= is_port(StdoutPort) -> {ok, State}; log_config_stdout_set(Stdout, #state{stdout = StdoutPort} = State) -> NewStdoutPort = if Stdout =:= true -> stdout_open(true); Stdout =:= false -> ok = stdout_close(StdoutPort), undefined end, {ok, State#state{stdout = NewStdoutPort}}. log_config_syslog_set(SyslogConfig, #state{syslog = SyslogOld, syslog_level = SyslogLevelOld} = State) -> SyslogLevelNew = case SyslogConfig of undefined -> undefined; #config_logging_syslog{level = SyslogLevelNew0} -> SyslogLevelNew0 end, SwitchF = fun(StateSwitch) -> ok = syslog_close(SyslogOld), case SyslogConfig of undefined -> StateSwitch#state{syslog = undefined, syslog_level = undefined}; #config_logging_syslog{} -> StateSwitch#state{syslog = syslog_open(SyslogConfig), syslog_level = SyslogLevelNew} end end, if SyslogLevelNew /= SyslogLevelOld -> case ?LOG_INFO_T0("changing syslog loglevel from ~s to ~s", [SyslogLevelOld, SyslogLevelNew], State) of {ok, StateNew} -> {ok, SwitchF(StateNew)}; {{error, _}, _} = ErrorResult -> ErrorResult end; true -> {ok, SwitchF(State)} end. log_config_formatters_set(FormattersConfigNew, #state{formatters_level = FormattersLevelOld} = State) -> FormattersLevelNew = case FormattersConfigNew of undefined -> undefined; #config_logging_formatters{level = FormattersLevelNew0} -> FormattersLevelNew0 end, SwitchF = fun(StateSwitch) -> ok = cloudi_core_i_logger_sup:reconfigure(FormattersConfigNew), StateSwitch#state{formatters = FormattersConfigNew, formatters_level = FormattersLevelNew} end, if FormattersLevelNew /= FormattersLevelOld -> case ?LOG_INFO_T0("changing formatters loglevel from ~s to ~s", [FormattersLevelOld, FormattersLevelNew], State) of {ok, StateNew} -> {ok, SwitchF(StateNew)}; {{error, _}, _} = ErrorResult -> ErrorResult end; true -> {ok, SwitchF(State)} end. log_level_update(#state{main_level = MainLevel, level = LevelOld, mode = Mode, destination = Destination, syslog_level = SyslogLevel, formatters_level = FormattersLevel} = State) -> case log_level([MainLevel, SyslogLevel, FormattersLevel]) of LevelOld -> {ok, State}; LevelNew -> case load_interface_module(LevelNew, Mode, Destination) of {ok, Binary} -> StateNew = State#state{ interface_module = Binary, level = LevelNew}, ?LOG_INFO_T1("changed loglevel from ~s to ~s", [LevelOld, LevelNew], StateNew), {ok, StateNew}; {error, _} = Error -> {Error, State} end end. -spec format_line(Level :: cloudi_service_api:loglevel(), Timestamp :: erlang:timestamp(), Node :: node(), Pid :: pid() | string() | undefined, Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: arity() | undefined, MetaData :: any(), LogMessage :: iodata()) -> iolist(). % utf8 encoded strings format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) -> ModuleBin = erlang:atom_to_binary(Module, utf8), LineStr = if Line =:= 0 -> ""; is_integer(Line), Line > 0 -> int_to_dec_list(Line) end, FunctionArity = if Function =:= undefined -> ""; Arity =:= undefined -> erlang:atom_to_binary(Function, utf8); true -> [erlang:atom_to_binary(Function, utf8), [$/ | int_to_dec_list(Arity)]] end, PidStr = if is_pid(Pid) -> erlang:pid_to_list(Pid); is_list(Pid) -> Pid; Pid =:= undefined -> "" end, NodeBin = erlang:atom_to_binary(Node, utf8), MetaDataStr = if MetaData == [] -> ""; true -> io_lib:format("~p~n", [MetaData]) end, [timestamp_iso8601(Timestamp), $\s, log_level_to_string(Level), $\s, $(, ModuleBin, $:, LineStr, $:, FunctionArity, $:, PidStr, $:, NodeBin, $), $\n, MetaDataStr, LogMessage, $\n]. timestamp_iso8601({_, _, MicroSeconds} = Timestamp) -> % ISO 8601 for date/time http://www.w3.org/TR/NOTE-datetime {{DateYYYY, DateMM, DateDD}, {TimeHH, TimeMM, TimeSS}} = calendar:now_to_universal_time(Timestamp), [DateYYYY0, DateYYYY1, DateYYYY2, DateYYYY3] = int_to_dec_list(DateYYYY, 4, $0), [DateMM0, DateMM1] = int_to_dec_list(DateMM, 2, $0), [DateDD0, DateDD1] = int_to_dec_list(DateDD, 2, $0), [TimeHH0, TimeHH1] = int_to_dec_list(TimeHH, 2, $0), [TimeMM0, TimeMM1] = int_to_dec_list(TimeMM, 2, $0), [TimeSS0, TimeSS1] = int_to_dec_list(TimeSS, 2, $0), [MicroSeconds0, MicroSeconds1, MicroSeconds2, MicroSeconds3, MicroSeconds4, MicroSeconds5] = int_to_dec_list(MicroSeconds, 6, $0), [DateYYYY0, DateYYYY1, DateYYYY2, DateYYYY3, $-, DateMM0, DateMM1, $-, DateDD0, DateDD1, $T, TimeHH0, TimeHH1, $:, TimeMM0, TimeMM1, $:, TimeSS0, TimeSS1, $., MicroSeconds0, MicroSeconds1, MicroSeconds2, MicroSeconds3, MicroSeconds4, MicroSeconds5, $Z]. log_message_formatter_call(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, #config_logging_formatter{ output = undefined, formatter = Formatter, formatter_config = FormatterConfig}, #state{logger_node = ErrorNode, logger_self = ErrorSelf}) -> % A formatter module has: % required: format(Msg, Config) % optional: format(Msg, Config, Colors) Msg = lager_msg(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage), try Formatter:format(Msg, FormatterConfig) catch ErrorType:Error -> ErrorMessage = cloudi_string: format_to_binary("formatter(~p) ~p ~p~n~p", [Formatter, ErrorType, Error, erlang:get_stacktrace()]), [format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage), format_line(error, timestamp_increment(Timestamp), ErrorNode, ErrorSelf, ?MODULE, ?LINE, undefined, undefined, [], ErrorMessage)] end; log_message_formatter_call(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, #config_logging_formatter{ output = Output, output_name = OutputName}, #state{logger_node = ErrorNode, logger_self = ErrorSelf}) -> Msg = lager_msg(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage), try gen_event:notify(OutputName, {log, Msg}) of ok -> format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) catch error:badarg -> % output module is not currently running, % it likely exceeded the maximum restart intensity % (which is logged elsewhere via sasl) format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage); ErrorType:Error -> ErrorMessage = cloudi_string: format_to_binary("output(~p) ~p ~p~n~p", [Output, ErrorType, Error, erlang:get_stacktrace()]), [format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage), format_line(error, timestamp_increment(Timestamp), ErrorNode, ErrorSelf, ?MODULE, ?LINE, undefined, undefined, [], ErrorMessage)] end. log_message_formatter(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, #config_logging_formatter{ level = FormatterLevel} = FormatterConfig, State) -> case log_level_allowed(FormatterLevel, Level) of true -> log_message_formatter_call(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, FormatterConfig, State); false -> format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) end. log_message_formatters(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, undefined, _) -> format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage); log_message_formatters(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, #config_logging_formatters{ default = Default, lookup = Lookup}, State) -> case keys1value:find(Module, Lookup) of {ok, FormatterConfig} -> log_message_formatter(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, FormatterConfig, State); error -> if Default =:= undefined -> format_line(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage); true -> log_message_formatter(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, Default, State) end end. log_message_external(Mode, Process, Level, Module, Line, Function, Arity, Format, Args) when is_atom(Level), is_atom(Module), is_integer(Line), Line >= 0, is_atom(Function), (Arity =:= undefined) orelse (is_integer(Arity) andalso (Arity >= 0)) -> Timestamp = cloudi_timestamp:timestamp(), case flooding_logger(Timestamp) of {true, _} when Mode =:= async -> ok; {_, FloodingWarning} -> MetaData = metadata_get(), LogMessage0 = if is_list(Format), Args =:= undefined -> Format; true -> log_message_safe(Format, Args) end, LogMessageN = if FloodingWarning =:= undefined; Mode =:= sync -> LogMessage0; is_binary(FloodingWarning) -> [LogMessage0, FloodingWarning] end, if Mode =:= async -> gen_server:cast(Process, {Level, Timestamp, node(), self(), Module, Line, Function, Arity, MetaData, LogMessageN}); Mode =:= sync -> gen_server:call(Process, {Level, Timestamp, node(), self(), Module, Line, Function, Arity, MetaData, LogMessageN}, infinity) end end. flooding_logger_warning(SecondsRemaining) -> cloudi_string:format_to_binary("~n" "... (~w logged/second async stopped process logging for ~.2f seconds)", [1000000 div ?LOGGER_FLOODING_DELTA, SecondsRemaining]). %% determine if a single process has sent too many logging messages flooding_logger(Timestamp1) -> case erlang:get(?LOGGER_FLOODING_PDICT_KEY) of undefined -> erlang:put(?LOGGER_FLOODING_PDICT_KEY, {Timestamp1, 1, false}), {false, undefined}; {Timestamp0, Count0, Flooding} -> Count1 = Count0 + 1, MicroSecondsElapsed = timer:now_diff(Timestamp1, Timestamp0), if (MicroSecondsElapsed > ?LOGGER_FLOODING_INTERVAL_MAX * 1000) orelse (MicroSecondsElapsed < 0) -> erlang:put(?LOGGER_FLOODING_PDICT_KEY, {Timestamp1, 1, false}), {false, undefined}; (MicroSecondsElapsed > ?LOGGER_FLOODING_INTERVAL_MIN * 1000) andalso (MicroSecondsElapsed div Count1 < ?LOGGER_FLOODING_DELTA) -> erlang:put(?LOGGER_FLOODING_PDICT_KEY, {Timestamp0, Count1, true}), SecondsRemaining = ?LOGGER_FLOODING_INTERVAL_MAX * 1000.0 - MicroSecondsElapsed / 1000000, {false, flooding_logger_warning(SecondsRemaining)}; true -> erlang:put(?LOGGER_FLOODING_PDICT_KEY, {Timestamp0, Count1, Flooding}), {Flooding, undefined} end end. log_message_internal_t0(LevelCheck, Line, Function, Arity, Format, Args, #state{level = Level, logger_node = Node, logger_self = Self} = State) when LevelCheck =:= fatal; LevelCheck =:= error; LevelCheck =:= warn; LevelCheck =:= info; LevelCheck =:= debug; LevelCheck =:= trace -> case log_level_allowed(Level, LevelCheck) of true -> LogMessage = log_message(Format, Args), log_message_internal(LevelCheck, cloudi_timestamp:timestamp(), Node, Self, ?MODULE, Line, Function, Arity, [], LogMessage, State); false -> {ok, State} end. log_message_internal_t1(LevelCheck, Line, Function, Arity, Format, Args, #state{level = Level, destination = Destination, logger_node = Node, logger_self = Self}) when LevelCheck =:= fatal; LevelCheck =:= error; LevelCheck =:= warn; LevelCheck =:= info; LevelCheck =:= debug; LevelCheck =:= trace -> case log_level_allowed(Level, LevelCheck) of true -> LogMessage = log_message(Format, Args), gen_server:cast(Destination, {LevelCheck, cloudi_timestamp:timestamp(), Node, Self, ?MODULE, Line, Function, Arity, [], LogMessage}); false -> ok end. log_message_internal(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, #state{main_level = MainLevel, stdout = StdoutPort, syslog_level = SyslogLevel, formatters = FormattersConfig, aspects_log_before = AspectsLogBefore, aspects_log_after = AspectsLogAfter} = State) when Level =:= fatal; Level =:= error; Level =:= warn; Level =:= info; Level =:= debug; Level =:= trace -> Message = log_message_formatters(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage, FormattersConfig, State), ok = aspects_log(AspectsLogBefore, Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage), {FileResult, NewState} = case log_level_allowed(MainLevel, Level) of true -> ok = log_stdout(Message, StdoutPort), log_file(Message, State); false -> {ok, State} end, case log_level_allowed(SyslogLevel, Level) of true -> ok = log_syslog(Level, Timestamp, Message, NewState); false -> ok end, ok = aspects_log(AspectsLogAfter, Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage), {FileResult, NewState}. -spec log_message_safe(Format :: list(), Args :: list()) -> binary(). log_message_safe(Format, Args) -> try log_message(Format, Args) catch error:badarg -> cloudi_string:format_to_binary("INVALID LOG INPUT: ~p ~p", [Format, Args]) end. -spec log_message(Format :: list(), Args :: list()) -> binary(). log_message(Format, Args) -> LogMessageUnicode = cloudi_string:format(Format, Args), unicode:characters_to_binary(LogMessageUnicode). log_level([_ | _] = L) -> cloudi_core_i_configuration:logging_level_highest([off | L]). log_file(_, #state{file_path = undefined} = State) -> {ok, State}; log_file(Message, #state{file_path = FilePath, fd = FdOld, inode = InodeOld} = State) -> case file:read_file_info(FilePath, [raw]) of {ok, #file_info{inode = CurrentInode}} -> if CurrentInode == InodeOld -> file:write(FdOld, Message), file:datasync(FdOld), {ok, State}; true -> file:close(FdOld), case log_file_reopen(FilePath, CurrentInode, State) of {ok, #state{fd = FdNew} = StateNew} -> file:write(FdNew, Message), file:datasync(FdNew), {ok, StateNew}; {error, _} = Error -> {Error, State#state{fd = undefined, inode = undefined}} end end; {error, enoent} -> file:close(FdOld), case log_file_open(FilePath, State) of {ok, #state{fd = FdNew} = StateNew} -> file:write(FdNew, Message), file:datasync(FdNew), {ok, StateNew}; {error, _} = Error -> {Error, State#state{fd = undefined, inode = undefined}} end; {error, _} = Error -> {Error, State} end. log_file_open(undefined, State) -> {ok, State}; log_file_open(FilePath, State) -> case file:open(FilePath, [raw, append]) of {ok, Fd} -> case file:read_file_info(FilePath, [raw]) of {ok, #file_info{inode = Inode}} -> {ok, State#state{file_path = FilePath, fd = Fd, inode = Inode}}; {error, _} = Error -> Error end; {error, _} = Error -> Error end. log_file_reopen(FilePath, Inode, State) -> case file:open(FilePath, [raw, append]) of {ok, Fd} -> {ok, State#state{file_path = FilePath, fd = Fd, inode = Inode}}; {error, _} = Error -> Error end. log_stdout(_, undefined) -> ok; log_stdout(Message, StdoutPort) when is_port(StdoutPort) -> true = erlang:port_command(StdoutPort, Message), ok. log_syslog(Level, Timestamp, Message, #state{syslog = Syslog}) -> SyslogSeverity = log_level_to_syslog_severity(Level), ok = syslog_socket:send(Syslog, SyslogSeverity, Timestamp, Message), ok. log_redirect(_, Destination, #state{destination = Destination} = State) -> {ok, State}; log_redirect(Node, DestinationNew, #state{level = Level, mode = Mode} = State) -> NodeLogger = if DestinationNew =:= ?MODULE -> node(); true -> Node end, case ?LOG_INFO_T0("redirecting log output to ~ts", [NodeLogger], State) of {ok, StateNext} -> case load_interface_module(Level, Mode, DestinationNew) of {ok, Binary} -> ?LOG_INFO_T1("redirected log output from ~ts to ~ts", [node(), NodeLogger], StateNext), {ok, StateNext#state{interface_module = Binary, destination = DestinationNew}}; {error, Reason} -> {error, Reason, StateNext} end; {{error, _}, _} = Error -> Error end. log_mode_check(#state{level = Level, mode = ModeOld, destination = Destination, logger_self = Self} = State) -> {message_queue_len, MessageQueueLength} = erlang:process_info(Self, message_queue_len), ModeNew = if ModeOld =:= async, MessageQueueLength >= ?LOGGER_MSG_QUEUE_SYNC -> sync; ModeOld =:= sync, MessageQueueLength =< ?LOGGER_MSG_QUEUE_ASYNC -> async; true -> ModeOld end, if ModeNew /= ModeOld -> case load_interface_module(Level, ModeNew, Destination) of {ok, Binary} -> {ok, State#state{interface_module = Binary, mode = ModeNew}}; {error, _} = Error -> Error end; true -> {ok, State} end. log_level_to_string(fatal) -> "FATAL"; log_level_to_string(error) -> "ERROR"; log_level_to_string(warn) -> "WARN "; log_level_to_string(info) -> "INFO "; log_level_to_string(debug) -> "DEBUG"; log_level_to_string(trace) -> "TRACE". log_level_to_syslog_severity(fatal) -> critical; log_level_to_syslog_severity(error) -> error; log_level_to_syslog_severity(warn) -> warning; log_level_to_syslog_severity(info) -> notice; log_level_to_syslog_severity(debug) -> informational; log_level_to_syslog_severity(trace) -> debug. log_level_allowed(trace, fatal) -> true; log_level_allowed(trace, error) -> true; log_level_allowed(trace, warn) -> true; log_level_allowed(trace, info) -> true; log_level_allowed(trace, debug) -> true; log_level_allowed(trace, trace) -> true; log_level_allowed(debug, fatal) -> true; log_level_allowed(debug, error) -> true; log_level_allowed(debug, warn) -> true; log_level_allowed(debug, info) -> true; log_level_allowed(debug, debug) -> true; log_level_allowed(info, fatal) -> true; log_level_allowed(info, error) -> true; log_level_allowed(info, warn) -> true; log_level_allowed(info, info) -> true; log_level_allowed(warn, fatal) -> true; log_level_allowed(warn, error) -> true; log_level_allowed(warn, warn) -> true; log_level_allowed(error, fatal) -> true; log_level_allowed(error, error) -> true; log_level_allowed(fatal, fatal) -> true; log_level_allowed(_, _) -> false. timestamp_increment({MegaSecs, Secs, MicroSecs}) -> MicroSecsNew = MicroSecs + 1, SecsNew = Secs + (MicroSecsNew div 1000000), {MegaSecs + (SecsNew div 1000000), SecsNew rem 1000000, MicroSecsNew rem 1000000}. -define(INTERFACE_MODULE_HEADER, " -module(cloudi_core_i_logger_interface). -author('mjtruog [at] gmail (dot) com'). -export([fatal/6, error/6, warn/6, info/6, debug/6, trace/6, fatal_sync/6, error_sync/6, warn_sync/6, info_sync/6, debug_sync/6, trace_sync/6, fatal_apply/2, error_apply/2, warn_apply/2, info_apply/2, debug_apply/2, trace_apply/2, fatal_apply/3, error_apply/3, warn_apply/3, info_apply/3, debug_apply/3, trace_apply/3])."). interface(off, _, _) -> ?INTERFACE_MODULE_HEADER " fatal(_, _, _, _, _, _) -> ok. error(_, _, _, _, _, _) -> ok. warn(_, _, _, _, _, _) -> ok. info(_, _, _, _, _, _) -> ok. debug(_, _, _, _, _, _) -> ok. trace(_, _, _, _, _, _) -> ok. fatal_sync(_, _, _, _, _, _) -> ok. error_sync(_, _, _, _, _, _) -> ok. warn_sync(_, _, _, _, _, _) -> ok. info_sync(_, _, _, _, _, _) -> ok. debug_sync(_, _, _, _, _, _) -> ok. trace_sync(_, _, _, _, _, _) -> ok. fatal_apply(_, _) -> undefined. error_apply(_, _) -> undefined. warn_apply(_, _) -> undefined. info_apply(_, _) -> undefined. debug_apply(_, _) -> undefined. trace_apply(_, _) -> undefined. fatal_apply(_, _, _) -> undefined. error_apply(_, _, _) -> undefined. warn_apply(_, _, _) -> undefined. info_apply(_, _, _) -> undefined. debug_apply(_, _, _) -> undefined. trace_apply(_, _, _) -> undefined. "; interface(fatal, Mode, Process) -> cloudi_string:format( ?INTERFACE_MODULE_HEADER " fatal(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(~w, ~w, Module, Line, Function, Arity, Format, Arguments). error(_, _, _, _, _, _) -> ok. warn(_, _, _, _, _, _) -> ok. info(_, _, _, _, _, _) -> ok. debug(_, _, _, _, _, _) -> ok. trace(_, _, _, _, _, _) -> ok. fatal_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(sync, ~w, Module, Line, Function, Arity, Format, Arguments). error_sync(_, _, _, _, _, _) -> ok. warn_sync(_, _, _, _, _, _) -> ok. info_sync(_, _, _, _, _, _) -> ok. debug_sync(_, _, _, _, _, _) -> ok. trace_sync(_, _, _, _, _, _) -> ok. fatal_apply(F, A) -> erlang:apply(F, A). error_apply(_, _) -> undefined. warn_apply(_, _) -> undefined. info_apply(_, _) -> undefined. debug_apply(_, _) -> undefined. trace_apply(_, _) -> undefined. fatal_apply(M, F, A) -> erlang:apply(M, F, A). error_apply(_, _, _) -> undefined. warn_apply(_, _, _) -> undefined. info_apply(_, _, _) -> undefined. debug_apply(_, _, _) -> undefined. trace_apply(_, _, _) -> undefined. ", [Mode, Process, Process]); interface(error, Mode, Process) -> cloudi_string:format( ?INTERFACE_MODULE_HEADER " fatal(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(~w, ~w, Module, Line, Function, Arity, Format, Arguments). error(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(~w, ~w, Module, Line, Function, Arity, Format, Arguments). warn(_, _, _, _, _, _) -> ok. info(_, _, _, _, _, _) -> ok. debug(_, _, _, _, _, _) -> ok. trace(_, _, _, _, _, _) -> ok. fatal_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(sync, ~w, Module, Line, Function, Arity, Format, Arguments). error_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(sync, ~w, Module, Line, Function, Arity, Format, Arguments). warn_sync(_, _, _, _, _, _) -> ok. info_sync(_, _, _, _, _, _) -> ok. debug_sync(_, _, _, _, _, _) -> ok. trace_sync(_, _, _, _, _, _) -> ok. fatal_apply(F, A) -> erlang:apply(F, A). error_apply(F, A) -> erlang:apply(F, A). warn_apply(_, _) -> undefined. info_apply(_, _) -> undefined. debug_apply(_, _) -> undefined. trace_apply(_, _) -> undefined. fatal_apply(M, F, A) -> erlang:apply(M, F, A). error_apply(M, F, A) -> erlang:apply(M, F, A). warn_apply(_, _, _) -> undefined. info_apply(_, _, _) -> undefined. debug_apply(_, _, _) -> undefined. trace_apply(_, _, _) -> undefined. ", [Mode, Process, Mode, Process, Process, Process]); interface(warn, Mode, Process) -> cloudi_string:format( ?INTERFACE_MODULE_HEADER " fatal(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(~w, ~w, Module, Line, Function, Arity, Format, Arguments). error(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(~w, ~w, Module, Line, Function, Arity, Format, Arguments). warn(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(~w, ~w, Module, Line, Function, Arity, Format, Arguments). info(_, _, _, _, _, _) -> ok. debug(_, _, _, _, _, _) -> ok. trace(_, _, _, _, _, _) -> ok. fatal_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(sync, ~w, Module, Line, Function, Arity, Format, Arguments). error_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(sync, ~w, Module, Line, Function, Arity, Format, Arguments). warn_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(sync, ~w, Module, Line, Function, Arity, Format, Arguments). info_sync(_, _, _, _, _, _) -> ok. debug_sync(_, _, _, _, _, _) -> ok. trace_sync(_, _, _, _, _, _) -> ok. fatal_apply(F, A) -> erlang:apply(F, A). error_apply(F, A) -> erlang:apply(F, A). warn_apply(F, A) -> erlang:apply(F, A). info_apply(_, _) -> undefined. debug_apply(_, _) -> undefined. trace_apply(_, _) -> undefined. fatal_apply(M, F, A) -> erlang:apply(M, F, A). error_apply(M, F, A) -> erlang:apply(M, F, A). warn_apply(M, F, A) -> erlang:apply(M, F, A). info_apply(_, _, _) -> undefined. debug_apply(_, _, _) -> undefined. trace_apply(_, _, _) -> undefined. ", [Mode, Process, Mode, Process, Mode, Process, Process, Process, Process]); interface(info, Mode, Process) -> cloudi_string:format( ?INTERFACE_MODULE_HEADER " fatal(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(~w, ~w, Module, Line, Function, Arity, Format, Arguments). error(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(~w, ~w, Module, Line, Function, Arity, Format, Arguments). warn(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(~w, ~w, Module, Line, Function, Arity, Format, Arguments). info(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:info(~w, ~w, Module, Line, Function, Arity, Format, Arguments). debug(_, _, _, _, _, _) -> ok. trace(_, _, _, _, _, _) -> ok. fatal_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(sync, ~w, Module, Line, Function, Arity, Format, Arguments). error_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(sync, ~w, Module, Line, Function, Arity, Format, Arguments). warn_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(sync, ~w, Module, Line, Function, Arity, Format, Arguments). info_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:info(sync, ~w, Module, Line, Function, Arity, Format, Arguments). debug_sync(_, _, _, _, _, _) -> ok. trace_sync(_, _, _, _, _, _) -> ok. fatal_apply(F, A) -> erlang:apply(F, A). error_apply(F, A) -> erlang:apply(F, A). warn_apply(F, A) -> erlang:apply(F, A). info_apply(F, A) -> erlang:apply(F, A). debug_apply(_, _) -> undefined. trace_apply(_, _) -> undefined. fatal_apply(M, F, A) -> erlang:apply(M, F, A). error_apply(M, F, A) -> erlang:apply(M, F, A). warn_apply(M, F, A) -> erlang:apply(M, F, A). info_apply(M, F, A) -> erlang:apply(M, F, A). debug_apply(_, _, _) -> undefined. trace_apply(_, _, _) -> undefined. ", [Mode, Process, Mode, Process, Mode, Process, Mode, Process, Process, Process, Process, Process]); interface(debug, Mode, Process) -> cloudi_string:format( ?INTERFACE_MODULE_HEADER " fatal(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(~w, ~w, Module, Line, Function, Arity, Format, Arguments). error(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(~w, ~w, Module, Line, Function, Arity, Format, Arguments). warn(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(~w, ~w, Module, Line, Function, Arity, Format, Arguments). info(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:info(~w, ~w, Module, Line, Function, Arity, Format, Arguments). debug(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:debug(~w, ~w, Module, Line, Function, Arity, Format, Arguments). trace(_, _, _, _, _, _) -> ok. fatal_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(sync, ~w, Module, Line, Function, Arity, Format, Arguments). error_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(sync, ~w, Module, Line, Function, Arity, Format, Arguments). warn_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(sync, ~w, Module, Line, Function, Arity, Format, Arguments). info_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:info(sync, ~w, Module, Line, Function, Arity, Format, Arguments). debug_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:debug(sync, ~w, Module, Line, Function, Arity, Format, Arguments). trace_sync(_, _, _, _, _, _) -> ok. fatal_apply(F, A) -> erlang:apply(F, A). error_apply(F, A) -> erlang:apply(F, A). warn_apply(F, A) -> erlang:apply(F, A). info_apply(F, A) -> erlang:apply(F, A). debug_apply(F, A) -> erlang:apply(F, A). trace_apply(_, _) -> undefined. fatal_apply(M, F, A) -> erlang:apply(M, F, A). error_apply(M, F, A) -> erlang:apply(M, F, A). warn_apply(M, F, A) -> erlang:apply(M, F, A). info_apply(M, F, A) -> erlang:apply(M, F, A). debug_apply(M, F, A) -> erlang:apply(M, F, A). trace_apply(_, _, _) -> undefined. ", [Mode, Process, Mode, Process, Mode, Process, Mode, Process, Mode, Process, Process, Process, Process, Process, Process]); interface(trace, Mode, Process) -> cloudi_string:format( ?INTERFACE_MODULE_HEADER " fatal(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(~w, ~w, Module, Line, Function, Arity, Format, Arguments). error(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(~w, ~w, Module, Line, Function, Arity, Format, Arguments). warn(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(~w, ~w, Module, Line, Function, Arity, Format, Arguments). info(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:info(~w, ~w, Module, Line, Function, Arity, Format, Arguments). debug(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:debug(~w, ~w, Module, Line, Function, Arity, Format, Arguments). trace(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:trace(~w, ~w, Module, Line, Function, Arity, Format, Arguments). fatal_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:fatal(sync, ~w, Module, Line, Function, Arity, Format, Arguments). error_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:error(sync, ~w, Module, Line, Function, Arity, Format, Arguments). warn_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:warn(sync, ~w, Module, Line, Function, Arity, Format, Arguments). info_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:info(sync, ~w, Module, Line, Function, Arity, Format, Arguments). debug_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:debug(sync, ~w, Module, Line, Function, Arity, Format, Arguments). trace_sync(Module, Line, Function, Arity, Format, Arguments) -> cloudi_core_i_logger:trace(sync, ~w, Module, Line, Function, Arity, Format, Arguments). fatal_apply(F, A) -> erlang:apply(F, A). error_apply(F, A) -> erlang:apply(F, A). warn_apply(F, A) -> erlang:apply(F, A). info_apply(F, A) -> erlang:apply(F, A). debug_apply(F, A) -> erlang:apply(F, A). trace_apply(F, A) -> erlang:apply(F, A). fatal_apply(M, F, A) -> erlang:apply(M, F, A). error_apply(M, F, A) -> erlang:apply(M, F, A). warn_apply(M, F, A) -> erlang:apply(M, F, A). info_apply(M, F, A) -> erlang:apply(M, F, A). debug_apply(M, F, A) -> erlang:apply(M, F, A). trace_apply(M, F, A) -> erlang:apply(M, F, A). ", [Mode, Process, Mode, Process, Mode, Process, Mode, Process, Mode, Process, Mode, Process, Process, Process, Process, Process, Process, Process]). load_interface_module(undefined, _, _) -> {error, logging_level_undefined}; load_interface_module(Level, Mode, Process) when is_atom(Level) -> {ok, Module, Binary} = merl:compile(merl:quote(interface(Level, Mode, Process))), cloudi_core_i_logger_interface = Module, % make sure no old code exists code:purge(Module), % load the new current code case code:load_binary(Module, erlang:atom_to_list(Module) ++ ".erl", Binary) of {module, Module} -> % remove the old code code:soft_purge(Module), {ok, Binary}; {error, _} = Error -> Error end. stdout_open(false) -> undefined; stdout_open(true) -> erlang:open_port({fd, 0, 1}, [out, stream]). stdout_close(undefined) -> ok; stdout_close(StdoutPort) when is_port(StdoutPort) -> _ = (catch erlang:port_close(StdoutPort)), ok. syslog_open(#config_logging_syslog{identity = SyslogIdentity, facility = SyslogFacility, transport = SyslogTransport, transport_options = SyslogTransportOptions, protocol = SyslogProtocol, path = SyslogPath, host = SyslogHost, port = SyslogPort}) -> Options = [{app_name, SyslogIdentity}, {facility, SyslogFacility}, {transport, SyslogTransport}, {transport_options, SyslogTransportOptions}, {protocol, SyslogProtocol}, {utf8, true}, {path, SyslogPath}, {host, SyslogHost}, {port, SyslogPort}, {timeout, 5000}], {ok, Syslog} = syslog_socket:start_link(Options), Syslog. syslog_close(undefined) -> ok; syslog_close(Syslog) when is_pid(Syslog) -> syslog_socket:stop_link(Syslog). % asynchronous stop aspects_log([], _, _, _, _, _, _, _, _, _, _) -> ok; aspects_log([{M, F} | L], Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) -> try M:F(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) of _ -> aspects_log(L, Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) catch _:_ -> aspects_log(L, Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) end; aspects_log([F | L], Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) -> try F(Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) of _ -> aspects_log(L, Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) catch _:_ -> aspects_log(L, Level, Timestamp, Node, Pid, Module, Line, Function, Arity, MetaData, LogMessage) end. int_to_dec_list(I) when is_integer(I), I >= 0 -> int_to_dec_list([], I). int_to_dec_list(L, I) when I < 10 -> [int_to_dec(I) | L]; int_to_dec_list(L, I) -> int_to_dec_list([int_to_dec(I rem 10) | L], I div 10). int_to_dec_list(I, N, Char) when is_integer(I), I >= 0 -> int_to_dec_list([], I, 1, N, Char). int_to_dec_list(L, I, Count, N, Char) when I < 10 -> int_to_list_pad([int_to_dec(I) | L], N - Count, Char); int_to_dec_list(L, I, Count, N, Char) -> int_to_dec_list([int_to_dec(I rem 10) | L], I div 10, Count + 1, N, Char). int_to_list_pad(L, 0, _) -> L; int_to_list_pad(L, Count, Char) -> int_to_list_pad([Char | L], Count - 1, Char). int_to_dec(I) when 0 =< I, I =< 9 -> I + $0. -spec eval(L :: list({any(), fun((any(), #state{}) -> {ok, #state{}} | {{error, any()}, #state{}})}), State :: #state{}) -> {ok, #state{}} | {{error, any()}, #state{}}. eval([], State) -> {ok, State}; eval([{Value, F} | L], State) -> case F(Value, State) of {ok, NewState} -> eval(L, NewState); {{error, _}, _} = Error -> Error end. %%%------------------------------------------------------------------------ %%% lager integration based on lager source code %%% (lager is under the Apache version 2.0 license and %%% was developed by Basho Technologies) %%%------------------------------------------------------------------------ % from lager module -define(LAGER_MD_KEY, '__lager_metadata'). % from lager:md/0 -spec lager_metadata_get() -> list({atom(), any()}). lager_metadata_get() -> case erlang:get(?LAGER_MD_KEY) of undefined -> []; L -> L end. % from lager:md/1 -spec lager_metadata_set(list({atom(), any()})) -> ok. lager_metadata_set(L) when is_list(L) -> case lists:all(fun({Key, _Value}) when is_atom(Key) -> true; (_) -> false end, L) of true -> erlang:put(?LAGER_MD_KEY, L), ok; false -> erlang:error(badarg) end; lager_metadata_set(_) -> erlang:error(badarg). % based on lager_util:maybe_utc/1 lager_datetime({_, _, MicroSeconds} = Timestamp) -> UTC = case application:get_env(sasl, utc_log) of {ok, Val} -> Val; undefined -> case application:get_env(stdlib, utc_log) of {ok, Val} -> Val; undefined -> false end end, TimeMS = MicroSeconds div 1000 rem 1000, if UTC =:= true -> {Date, {TimeHH, TimeMM, TimeSS}} = calendar:now_to_universal_time(Timestamp), {utc, {Date, {TimeHH, TimeMM, TimeSS, TimeMS}}}; true -> {Date, {TimeHH, TimeMM, TimeSS}} = calendar:now_to_local_time(Timestamp), {Date, {TimeHH, TimeMM, TimeSS, TimeMS}} end. % from lager_util:i2l/1 lager_i2l(I) when I < 10 -> [$0, $0 + I]; lager_i2l(I) -> erlang:integer_to_list(I). % from lager_util:i3l/1 lager_i3l(I) when I < 100 -> [$0 | lager_i2l(I)]; lager_i3l(I) -> erlang:integer_to_list(I). % based on lager_util:format_time/1 lager_datetime_format({utc, {{Y, M, D}, {H, Mi, S, Ms}}}) -> {[erlang:integer_to_list(Y), $-, lager_i2l(M), $-, lager_i2l(D)], [lager_i2l(H), $:, lager_i2l(Mi), $:, lager_i2l(S), $., lager_i3l(Ms), $ , $U, $T, $C]}; lager_datetime_format({{Y, M, D}, {H, Mi, S, Ms}}) -> {[erlang:integer_to_list(Y), $-, lager_i2l(M), $-, lager_i2l(D)], [lager_i2l(H), $:, lager_i2l(Mi), $:, lager_i2l(S), $., lager_i3l(Ms)]}. % CloudI levels mapped to lager severity for formatters lager_severity_output(fatal) -> emergency; lager_severity_output(error) -> error; lager_severity_output(warn) -> warning; lager_severity_output(info) -> info; lager_severity_output(debug) -> debug; lager_severity_output(trace) -> debug. lager_severity_input(emergency) -> fatal; lager_severity_input(error) -> error; lager_severity_input(warning) -> warn; lager_severity_input(info) -> info; lager_severity_input(debug) -> debug. -record(lager_msg, { destinations :: list(), metadata :: list({atom(), any()}), severity :: debug | emergency | error | info | warning, datetime :: {string(), string()}, timestamp :: erlang:timestamp(), message :: list() }). -spec lager_msg(Level :: debug | error | fatal | info | trace | warn, Timestamp :: erlang:timestamp(), Node :: node(), Pid :: pid(), Module :: atom(), Line :: non_neg_integer(), Function :: atom(), Arity :: non_neg_integer() | undefined, MetaData :: list({atom(), any()}), LogMessage :: iolist()) -> #lager_msg{}. % based on lager_msg:new/5 lager_msg(Level, Timestamp, Node, Pid, Module, Line, Function, _Arity, MetaData0, LogMessage) -> Destinations = [], % not using TraceFilters MetaData1 = if Function =:= undefined -> MetaData0; true -> [{function, Function} | MetaData0] end, MetaDataN = [{module, Module}, {line, Line}, {node, Node}, {pid, erlang:pid_to_list(Pid)} | MetaData1], Severity = lager_severity_output(Level), DateTime = lager_datetime_format(lager_datetime(Timestamp)), Message = if is_list(LogMessage) -> LogMessage; is_binary(LogMessage) -> [LogMessage] end, % create lager_msg record manually {lager_msg, Destinations, MetaDataN, Severity, DateTime, Timestamp, Message}.