Current section
Files
Jump to
Current section
Files
src/cloudi_core_i_logger.erl
%-*-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
%%%
%%% MIT License
%%%
%%% Copyright (c) 2009-2021 Michael Truog <mjtruog at protonmail dot com>
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a
%%% copy of this software and associated documentation files (the "Software"),
%%% to deal in the Software without restriction, including without limitation
%%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
%%% and/or sell copies of the Software, and to permit persons to whom the
%%% Software is furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
%%% DEALINGS IN THE SOFTWARE.
%%%
%%% @author Michael Truog <mjtruog at protonmail dot com>
%%% @copyright 2009-2021 Michael Truog
%%% @version 2.0.2 {@date} {@time}
%%%------------------------------------------------------------------------
-module(cloudi_core_i_logger).
-author('mjtruog at protonmail dot com').
-behaviour(gen_server).
%% external interface
-export([start_link/1,
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,
status/1,
status_reset/1,
metadata_get/0, metadata_set/1,
format/2, format/3,
microseconds_to_string/1,
milliseconds_to_string/1,
seconds_to_string/1,
datetime_to_string/1]).
%% 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("cloudi_core_i_logger.hrl").
-include_lib("kernel/include/file.hrl").
%% logging macros used only within this module
-define(LOG_AT_T0(Level, T, Format, Args, State),
log_message_internal_t0(Level, T, ?LINE, ?FUNCTION_NAME, ?FUNCTION_ARITY,
Format, Args, State)).
-define(LOG_T0(Level, Format, Args, State),
log_message_internal_t0(Level, ?LINE, ?FUNCTION_NAME, ?FUNCTION_ARITY,
Format, Args, State)).
-define(LOG_T1(Level, Format, Args, State),
log_message_internal_t1(Level, ?LINE, ?FUNCTION_NAME, ?FUNCTION_ARITY,
Format, Args, State)).
-define(LOG_T0_ERROR(Format, Args, State),
?LOG_T0(error, Format, Args, State)).
-define(LOG_T0_INFO(Format, Args, State),
?LOG_T0(info, Format, Args, State)).
-define(LOG_T1_INFO(Format, Args, State),
?LOG_T1(info, Format, Args, State)).
-define(TERMINATE_DELAY, 1000). % milliseconds
-type mode_process() :: async | sync | overload.
-type mode_interface() :: async | sync.
-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(),
queue_pending = 0
:: non_neg_integer(),
queue_mode_async
:: pos_integer(),
queue_mode_sync
:: pos_integer(),
queue_mode_overload
:: pos_integer(),
mode = async
:: mode_process(),
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(),
log_time_offset
:: cloudi_service_api:loglevel(),
log_time_offset_nanoseconds
:: integer(),
log_time_offset_monitor
:: reference(),
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(),
mode_sync_start = undefined
:: undefined | cloudi_timestamp:native_monotonic(),
mode_sync_start_event = undefined
:: undefined | cloudi_timestamp:iso8601(),
mode_sync_end = undefined
:: undefined | cloudi_timestamp:native_monotonic(),
mode_sync_end_event = undefined
:: undefined | cloudi_timestamp:iso8601(),
mode_sync_total = undefined
:: undefined | cloudi_service_api:nanoseconds_string(),
mode_overload_start = undefined
:: undefined | cloudi_timestamp:native_monotonic(),
mode_overload_start_event = undefined
:: undefined | cloudi_timestamp:iso8601(),
mode_overload_end = undefined
:: undefined | cloudi_timestamp:native_monotonic(),
mode_overload_end_event = undefined
:: undefined | cloudi_timestamp:iso8601(),
mode_overload_total = undefined
:: undefined | cloudi_service_api:nanoseconds_string(),
time_offset_change = undefined
:: undefined | cloudi_service_api:seconds_change_string(),
time_offset_event = undefined
:: undefined | cloudi_timestamp:iso8601(),
file_counts = #{}
:: #{cloudi_service_api:loglevel() := pos_integer()},
error_read_count = 0
:: non_neg_integer(),
error_read_types = []
:: list(file:posix() | badarg | terminated),
error_write_count = 0
:: non_neg_integer(),
error_write_types = []
:: list(file:posix() | badarg | terminated),
error_sync_count = 0
:: non_neg_integer(),
error_sync_types = []
:: list(file:posix() | badarg | terminated)
}).
-define(LAGER_MD_KEY, '__lager_metadata'). % from lager module
%%%------------------------------------------------------------------------
%%% 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
%% ===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 filepath_exists(FilePath) of
ok ->
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 filepath_exists(FilePath) of
ok ->
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(ModeInterface :: mode_interface(),
Process :: atom() | {atom(), node()},
Module :: atom(),
Line :: non_neg_integer(),
Function :: atom(),
Arity :: arity() | undefined,
Format :: string(),
Args :: list() | undefined) ->
'ok'.
fatal(ModeInterface, Process, Module, Line, Function, Arity, Format, Args) ->
log_message_external(ModeInterface, 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(ModeInterface :: mode_interface(),
Process :: atom() | {atom(), node()},
Module :: atom(),
Line :: non_neg_integer(),
Function :: atom(),
Arity :: arity() | undefined,
Format :: string(),
Args :: list() | undefined) ->
'ok'.
error(ModeInterface, Process, Module, Line, Function, Arity, Format, Args) ->
log_message_external(ModeInterface, 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(ModeInterface :: mode_interface(),
Process :: atom() | {atom(), node()},
Module :: atom(),
Line :: non_neg_integer(),
Function :: atom(),
Arity :: arity() | undefined,
Format :: string(),
Args :: list() | undefined) ->
'ok'.
warn(ModeInterface, Process, Module, Line, Function, Arity, Format, Args) ->
log_message_external(ModeInterface, 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(ModeInterface :: mode_interface(),
Process :: atom() | {atom(), node()},
Module :: atom(),
Line :: non_neg_integer(),
Function :: atom(),
Arity :: arity() | undefined,
Format :: string(),
Args :: list() | undefined) ->
'ok'.
info(ModeInterface, Process, Module, Line, Function, Arity, Format, Args) ->
log_message_external(ModeInterface, 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(ModeInterface :: mode_interface(),
Process :: atom() | {atom(), node()},
Module :: atom(),
Line :: non_neg_integer(),
Function :: atom(),
Arity :: arity() | undefined,
Format :: string(),
Args :: list() | undefined) ->
'ok'.
debug(ModeInterface, Process, Module, Line, Function, Arity, Format, Args) ->
log_message_external(ModeInterface, 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(ModeInterface :: mode_interface(),
Process :: atom() | {atom(), node()},
Module :: atom(),
Line :: non_neg_integer(),
Function :: atom(),
Arity :: arity() | undefined,
Format :: string(),
Args :: list() | undefined) ->
'ok'.
trace(ModeInterface, Process, Module, Line, Function, Arity, Format, Args) ->
log_message_external(ModeInterface, Process, trace, Module, Line,
Function, Arity, Format, Args).
%%-------------------------------------------------------------------------
%% @doc
%% ===Get logging status.===
%% @end
%%-------------------------------------------------------------------------
-spec status(Timeout :: pos_integer() | infinity) ->
{ok, cloudi_service_api:logging_status()} | {error, timeout | noproc}.
status(Timeout) ->
?CATCH_EXIT(gen_server:call(?MODULE, status, Timeout)).
%%-------------------------------------------------------------------------
%% @doc
%% ===Reset logging status.===
%% @end
%%-------------------------------------------------------------------------
-spec status_reset(Timeout :: pos_integer() | infinity) ->
ok | {error, timeout | noproc}.
status_reset(Timeout) ->
?CATCH_EXIT(gen_server:call(?MODULE, status_reset, Timeout)).
%%-------------------------------------------------------------------------
%% @doc
%% ===Get metadata.===
%% @end
%%-------------------------------------------------------------------------
-spec metadata_get() ->
list({atom(), any()}) | #{}.
metadata_get() ->
case erlang:get(?LOGGER_METADATA_PDICT_KEY) of
?LAGER_MD_KEY ->
lager_metadata_get();
undefined ->
[];
Map when is_map(Map) ->
Map
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Set metadata.===
%% @end
%%-------------------------------------------------------------------------
-spec metadata_set(list({atom(), any()}) | #{}) ->
ok.
metadata_set(L)
when is_list(L) ->
erlang:put(?LOGGER_METADATA_PDICT_KEY, ?LAGER_MD_KEY),
lager_metadata_set(L);
metadata_set(Map)
when is_map(Map) ->
erlang:put(?LOGGER_METADATA_PDICT_KEY, Map),
ok.
%%-------------------------------------------------------------------------
%% @doc
%% ===Lager formatter example with default output.===
%% @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()) ->
iolist().
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 |
MetaDataNew] = cloudi_proplists:take_values(Defaults, MetaData),
LogMessage = Message,
format_line(Level, Timestamp, Node, PidStr,
Module, Line, Function, undefined,
MetaDataNew, LogMessage).
%%-------------------------------------------------------------------------
%% @doc
%% ===Create an ISO8601 timestamp from microseconds since the UNIX epoch.===
%% (The UNIX epoch is 1970-01-01T00:00:00.000000Z)
%% @end
%%-------------------------------------------------------------------------
-spec microseconds_to_string(TotalMicroSeconds :: non_neg_integer()) ->
cloudi_timestamp:iso8601().
microseconds_to_string(TotalMicroSeconds) ->
TotalSeconds = TotalMicroSeconds div 1000000,
MegaSeconds = TotalSeconds div 1000000,
Seconds = TotalSeconds - MegaSeconds * 1000000,
MicroSeconds = TotalMicroSeconds - TotalSeconds * 1000000,
timestamp_iso8601({MegaSeconds, Seconds, MicroSeconds}).
%%-------------------------------------------------------------------------
%% @doc
%% ===Create an ISO8601 timestamp from milliseconds since the UNIX epoch.===
%% (The UNIX epoch is 1970-01-01T00:00:00.000Z)
%% @end
%%-------------------------------------------------------------------------
-spec milliseconds_to_string(TotalMilliSeconds :: non_neg_integer()) ->
cloudi_timestamp:iso8601().
milliseconds_to_string(TotalMilliSeconds) ->
TotalSeconds = TotalMilliSeconds div 1000,
MegaSeconds = TotalSeconds div 1000000,
Seconds = TotalSeconds - MegaSeconds * 1000000,
MilliSeconds = TotalMilliSeconds - TotalSeconds * 1000,
[DateYYYY0, DateYYYY1, DateYYYY2, DateYYYY3, $-,
DateMM0, DateMM1, $-, DateDD0, DateDD1, $T,
TimeHH0, TimeHH1, $:, TimeMM0, TimeMM1, $:, TimeSS0, TimeSS1, $.,
MicroSeconds0, MicroSeconds1, MicroSeconds2, _, _, _,
$Z] = timestamp_iso8601({MegaSeconds, Seconds, MilliSeconds * 1000}),
[DateYYYY0, DateYYYY1, DateYYYY2, DateYYYY3, $-,
DateMM0, DateMM1, $-, DateDD0, DateDD1, $T,
TimeHH0, TimeHH1, $:, TimeMM0, TimeMM1, $:, TimeSS0, TimeSS1, $.,
MicroSeconds0, MicroSeconds1, MicroSeconds2, $Z].
%%-------------------------------------------------------------------------
%% @doc
%% ===Create an ISO8601 timestamp from seconds since the UNIX epoch.===
%% (The UNIX epoch is 1970-01-01T00:00:00Z)
%% @end
%%-------------------------------------------------------------------------
-spec seconds_to_string(TotalSeconds :: non_neg_integer()) ->
cloudi_timestamp:iso8601_seconds().
seconds_to_string(TotalSeconds) ->
MegaSeconds = TotalSeconds div 1000000,
Seconds = TotalSeconds - MegaSeconds * 1000000,
DateTimeUTC = calendar:now_to_universal_time({MegaSeconds, Seconds, 0}),
datetime_to_string(DateTimeUTC).
%%-------------------------------------------------------------------------
%% @doc
%% ===Create an ISO8601 timestamp from a datetime in UTC.===
%% @end
%%-------------------------------------------------------------------------
-spec datetime_to_string(DateTimeUTC :: calendar:datetime()) ->
cloudi_timestamp:iso8601_seconds().
datetime_to_string(DateTimeUTC) ->
datetime_iso8601(DateTimeUTC, undefined).
%%%------------------------------------------------------------------------
%%% Callback functions from gen_server
%%%------------------------------------------------------------------------
init([#config_logging{file = FilePath,
stdout = Stdout,
level = MainLevel,
queue_mode_async = QueueModeAsync,
queue_mode_sync = QueueModeSync,
queue_mode_overload = QueueModeOverload,
redirect = NodeLogger,
syslog = SyslogConfig,
formatters = FormattersConfig,
log_time_offset = LogTimeOffset,
aspects_log_before = AspectsLogBefore,
aspects_log_after = AspectsLogAfter}]) ->
% due to queue_mode sync and overload it is best to have this process
% running with high priority
normal = erlang:process_flag(priority, high),
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,
queue_mode_async = QueueModeAsync,
queue_mode_sync = QueueModeSync,
queue_mode_overload = QueueModeOverload,
formatters = FormattersConfig,
formatters_level = FormattersLevel,
log_time_offset = LogTimeOffset,
log_time_offset_nanoseconds = time_offset_nanoseconds(),
log_time_offset_monitor = erlang:monitor(time_offset,
clock_service),
aspects_log_before = AspectsLogBefore,
aspects_log_after = AspectsLogAfter,
logger_node = node(),
logger_self = self()},
{SyslogResult,
#state{syslog_level = SyslogLevel} = StateNext} = syslog_open(SyslogConfig,
false,
State),
Level = log_level([MainLevel, SyslogLevel, FormattersLevel]),
Destination = if
NodeLogger == node(); NodeLogger =:= undefined ->
?MODULE;
true ->
{?MODULE, NodeLogger}
end,
false = erlang:process_flag(trap_exit, true),
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, StateNew} ->
log_init(SyslogResult, StateNew);
{error, Reason} ->
{stop, Reason}
end;
{ok, Binary} ->
case ?LOG_T0_INFO("redirecting log output to ~ts",
[NodeLogger],
StateNext#state{file_path = FilePath,
interface_module = Binary,
level = Level,
destination = ?MODULE}) of
{ok, StateNew} ->
log_init(SyslogResult,
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(sync, Level, Timestamp, Node, Pid,
Module, Line, Function, Arity,
MetaData, LogMessage, State) of
{ok, StateNext} ->
case log_mode_check(Timestamp, StateNext) of
{ok, StateNew} ->
{reply, ok, StateNew};
{error, Reason} ->
{stop, Reason, ok, StateNext}
end;
{{error, Reason}, StateNext} ->
{stop, Reason, ok, StateNext}
end;
handle_call(status, _,
#state{mode = Mode,
mode_sync_start = SyncStart,
mode_sync_start_event = SyncStartEvent,
mode_sync_end = SyncEnd,
mode_sync_end_event = SyncEndEvent,
mode_sync_total = SyncTotal,
mode_overload_start = OverloadStart,
mode_overload_start_event = OverloadStartEvent,
mode_overload_end = OverloadEnd,
mode_overload_end_event = OverloadEndEvent,
mode_overload_total = OverloadTotal,
time_offset_change = TimeOffsetChange,
time_offset_event = TimeOffsetEvent,
file_counts = FileCounts,
error_read_count = ErrorReadCount,
error_read_types = ErrorReadTypes,
error_write_count = ErrorWriteCount,
error_write_types = ErrorWriteTypes,
error_sync_count = ErrorSyncCount,
error_sync_types = ErrorSyncTypes} = State) ->
TimeOffset = erlang:time_offset(),
Status0 = if
ErrorReadCount > 0 ->
[{file_read_fail_count, erlang:integer_to_list(ErrorReadCount)},
{file_read_fail_types, ErrorReadTypes}];
ErrorReadCount == 0 ->
[]
end,
Status1 = if
ErrorWriteCount > 0 ->
[{file_write_fail_count, erlang:integer_to_list(ErrorWriteCount)},
{file_write_fail_types, ErrorWriteTypes} | Status0];
ErrorWriteCount == 0 ->
Status0
end,
Status2 = if
ErrorSyncCount > 0 ->
[{file_sync_fail_count, erlang:integer_to_list(ErrorSyncCount)},
{file_sync_fail_types, ErrorSyncTypes} | Status1];
ErrorSyncCount == 0 ->
Status1
end,
Status3 = case maps:find(trace, FileCounts) of
{ok, FileCountTrace} ->
[{file_messages_trace,
erlang:integer_to_list(FileCountTrace)} | Status2];
error ->
Status2
end,
Status4 = case maps:find(debug, FileCounts) of
{ok, FileCountDebug} ->
[{file_messages_debug,
erlang:integer_to_list(FileCountDebug)} | Status3];
error ->
Status3
end,
Status5 = case maps:find(info, FileCounts) of
{ok, FileCountInfo} ->
[{file_messages_info,
erlang:integer_to_list(FileCountInfo)} | Status4];
error ->
Status4
end,
Status6 = case maps:find(warn, FileCounts) of
{ok, FileCountWarn} ->
[{file_messages_warn,
erlang:integer_to_list(FileCountWarn)} | Status5];
error ->
Status5
end,
Status7 = case maps:find(error, FileCounts) of
{ok, FileCountError} ->
[{file_messages_error,
erlang:integer_to_list(FileCountError)} | Status6];
error ->
Status6
end,
Status8 = case maps:find(fatal, FileCounts) of
{ok, FileCountFatal} ->
[{file_messages_fatal,
erlang:integer_to_list(FileCountFatal)} | Status7];
error ->
Status7
end,
Status9 = if
TimeOffsetChange =:= undefined ->
Status8;
is_list(TimeOffsetChange) ->
[{time_offset_last_change,
TimeOffsetChange},
{time_offset_last_event,
TimeOffsetEvent} | Status8]
end,
Status10 = if
OverloadStart =:= undefined ->
Status9;
OverloadEnd =:= undefined ->
OverloadStartMicroSeconds = cloudi_timestamp:
convert(OverloadStart + TimeOffset,
native, microsecond),
[{queue_mode_overload_last_start,
microseconds_to_string(OverloadStartMicroSeconds)},
{queue_mode_overload_last_start_event,
OverloadStartEvent} | Status9];
is_list(OverloadTotal) ->
OverloadStartMicroSeconds = cloudi_timestamp:
convert(OverloadStart + TimeOffset,
native, microsecond),
OverloadEndMicroSeconds = cloudi_timestamp:
convert(OverloadEnd + TimeOffset,
native, microsecond),
[{queue_mode_overload_last_start,
microseconds_to_string(OverloadStartMicroSeconds)},
{queue_mode_overload_last_start_event,
OverloadStartEvent},
{queue_mode_overload_last_end,
microseconds_to_string(OverloadEndMicroSeconds)},
{queue_mode_overload_last_end_event,
OverloadEndEvent},
{queue_mode_overload_last_total,
OverloadTotal} | Status9]
end,
Status11 = if
SyncStart =:= undefined ->
Status10;
SyncEnd =:= undefined ->
SyncStartMicroSeconds = cloudi_timestamp:
convert(SyncStart + TimeOffset,
native, microsecond),
[{queue_mode_sync_last_start,
microseconds_to_string(SyncStartMicroSeconds)},
{queue_mode_sync_last_start_event,
SyncStartEvent} | Status10];
is_list(SyncTotal) ->
SyncStartMicroSeconds = cloudi_timestamp:
convert(SyncStart + TimeOffset,
native, microsecond),
SyncEndMicroSeconds = cloudi_timestamp:
convert(SyncEnd + TimeOffset,
native, microsecond),
[{queue_mode_sync_last_start,
microseconds_to_string(SyncStartMicroSeconds)},
{queue_mode_sync_last_start_event,
SyncStartEvent},
{queue_mode_sync_last_end,
microseconds_to_string(SyncEndMicroSeconds)},
{queue_mode_sync_last_end_event,
SyncEndEvent},
{queue_mode_sync_last_total,
SyncTotal} | Status10]
end,
StatusN = [{queue_mode, Mode} | Status11],
{reply, {ok, StatusN}, State};
handle_call(status_reset, _, State) ->
{reply, ok,
State#state{mode_sync_start = undefined,
mode_sync_start_event = undefined,
mode_sync_end = undefined,
mode_sync_end_event = undefined,
mode_sync_total = undefined,
mode_overload_start = undefined,
mode_overload_start_event = undefined,
mode_overload_end = undefined,
mode_overload_end_event = undefined,
mode_overload_total = undefined,
time_offset_change = undefined,
time_offset_event = undefined,
file_counts = #{},
error_read_count = 0,
error_read_types = [],
error_write_count = 0,
error_write_types = [],
error_sync_count = 0,
error_sync_types = []}};
handle_call(Request, _, State) ->
{stop, cloudi_string:format("Unknown call \"~w\"", [Request]),
error, State}.
handle_cast({set, LoggingConfig}, State) ->
case log_config_set(LoggingConfig, State) of
{ok, StateNext} ->
case log_level_update(StateNext) of
{ok, StateNew} ->
{noreply, StateNew};
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
handle_cast({file_set, FilePath}, State) ->
case log_config_file_set(FilePath, State) of
{ok, StateNew} ->
{noreply, StateNew};
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
handle_cast({stdout_set, Stdout}, State) ->
{ok, StateNew} = log_config_stdout_set(Stdout, State),
{noreply, StateNew};
handle_cast({level_set, MainLevel}, State) ->
case log_config_main_level_set(MainLevel, State) of
{ok, StateNext} ->
case log_level_update(StateNext) of
{ok, StateNew} ->
{noreply, StateNew};
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
handle_cast({syslog_set, SyslogConfig}, State) ->
case log_config_syslog_set(SyslogConfig, State) of
{ok, StateNext} ->
case log_level_update(StateNext) of
{ok, StateNew} ->
{noreply, StateNew};
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
handle_cast({formatters_set, FormattersConfigNew}, State) ->
case log_config_formatters_set(FormattersConfigNew, State) of
{ok, StateNext} ->
case log_level_update(StateNext) of
{ok, StateNew} ->
{noreply, StateNew};
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
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(async, Level, Timestamp, Node, Pid,
Module, Line, Function, Arity,
MetaData, LogMessage, State) of
{ok, StateNext} ->
case log_mode_check(Timestamp, 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 \"~w\"", [Request]), State}.
handle_info({'DOWN', _, process, Process, Info},
#state{syslog = Syslog} = State) ->
{Entity, StateUpdated} = if
Process == Syslog ->
{"syslog",
State#state{syslog = undefined,
syslog_level = undefined}};
true ->
% will happen if syslog is stopped successfully (asynchronously)
% but should not occur with other processes
{io_lib:format("process(~w)", [Process]), State}
end,
case log_level_update(StateUpdated) of
{ok, StateNext}
when Info =:= normal ->
{noreply, StateNext};
{ok, StateNext} ->
case ?LOG_T0_ERROR("~s died: ~tw",
[Entity, Info], StateNext) of
{ok, StateNew} ->
{noreply, StateNew};
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
{{error, Reason}, StateNext} ->
{stop, Reason, StateNext}
end;
handle_info({'CHANGE', Monitor, time_offset, clock_service, TimeOffset},
#state{log_time_offset = LogTimeOffset,
log_time_offset_nanoseconds = NanoSecondsOld,
log_time_offset_monitor = Monitor} = State) ->
Timestamp = cloudi_timestamp:timestamp(),
NanoSecondsNew = time_offset_to_nanoseconds(TimeOffset),
TimeOffsetChange = nanoseconds_to_seconds_change_string(NanoSecondsOld,
NanoSecondsNew),
TimeOffsetEvent = timestamp_iso8601(Timestamp),
case ?LOG_AT_T0(LogTimeOffset, Timestamp,
"Erlang time_offset changed ~s",
[TimeOffsetChange],
State#state{log_time_offset_nanoseconds = NanoSecondsNew,
time_offset_change = TimeOffsetChange,
time_offset_event = TimeOffsetEvent}) of
{ok, StateNew} ->
{noreply, StateNew};
{{error, Reason}, StateNew} ->
{stop, Reason, StateNew}
end;
handle_info({'EXIT', _, Reason},
#state{logger_self = Self} = State) ->
if
Reason =:= shutdown;
element(1, Reason) =:= shutdown ->
TerminateTimeMax = cloudi_timestamp:milliseconds_monotonic() +
?TIMEOUT_TERMINATE_MAX,
true = ?TERMINATE_DELAY < ?TIMEOUT_TERMINATE_MAX,
_ = erlang:send_after(?TERMINATE_DELAY, Self,
{terminate, Reason, TerminateTimeMax}),
{noreply, State};
true ->
{stop, Reason, State}
end;
handle_info({terminate, Reason, TerminateTimeMax} = Terminate,
#state{logger_self = Self} = State) ->
{message_queue_len,
MessageQueueLength} = erlang:process_info(Self, message_queue_len),
TerminateDelay = if
MessageQueueLength > 0 ->
RemainingMilliSeconds = TerminateTimeMax -
cloudi_timestamp:milliseconds_monotonic(),
if
RemainingMilliSeconds =< 0 ->
undefined;
RemainingMilliSeconds < ?TERMINATE_DELAY ->
RemainingMilliSeconds;
true ->
?TERMINATE_DELAY
end;
true ->
undefined
end,
if
TerminateDelay =:= undefined ->
{stop, Reason, State};
is_integer(TerminateDelay) ->
_ = erlang:send_after(TerminateDelay, Self, Terminate),
{noreply, State}
end;
handle_info(Request, State) ->
{stop, cloudi_string:format("Unknown info \"~w\"", [Request]), State}.
terminate(_, #state{fd = Fd,
stdout = StdoutPort,
syslog = Syslog,
log_time_offset_monitor = Monitor}) ->
_ = (catch file:close(Fd)),
ok = stdout_close(StdoutPort),
ok = syslog_close(Syslog),
true = erlang:demonitor(Monitor),
ok.
code_change(_, State, _) ->
{ok, State}.
%%%------------------------------------------------------------------------
%%% Private functions
%%%------------------------------------------------------------------------
log_init(ok, State) ->
{ok, State};
log_init({error, Reason}, State) ->
case ?LOG_T0_ERROR("syslog error: ~tp", [Reason], State) of
{ok, _} ->
{stop, syslog};
{{error, Reason}, _} ->
{stop, Reason}
end.
log_config_set(#config_logging{file = FilePath,
stdout = Stdout,
level = MainLevel,
queue_mode_async = QueueModeAsync,
queue_mode_sync = QueueModeSync,
queue_mode_overload = QueueModeOverload,
redirect = NodeLogger,
syslog = SyslogConfig,
formatters = FormattersConfig,
log_time_offset = LogTimeOffset,
aspects_log_before = AspectsLogBefore,
aspects_log_after = AspectsLogAfter},
State) ->
case accum([{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{queue_pending = 0,
queue_mode_async = QueueModeAsync,
queue_mode_sync = QueueModeSync,
queue_mode_overload = QueueModeOverload,
log_time_offset = LogTimeOffset,
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_T0_INFO("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_T0_INFO("changing file path from ~ts to ~ts",
[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) ->
StdoutPortNew = if
Stdout =:= true ->
stdout_open(true);
Stdout =:= false ->
ok = stdout_close(StdoutPort),
undefined
end,
{ok, State#state{stdout = StdoutPortNew}}.
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),
syslog_open(SyslogConfig, true, StateSwitch)
end,
if
SyslogLevelNew /= SyslogLevelOld ->
case ?LOG_T0_INFO("changing syslog loglevel from ~s to ~s",
[SyslogLevelOld, SyslogLevelNew], State) of
{ok, StateNew} ->
SwitchF(StateNew);
{{error, _}, _} = ErrorResult ->
ErrorResult
end;
true ->
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_T0_INFO("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_T1_INFO("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 == []; map_size(MetaData) == 0 ->
"";
true ->
io_lib:format("~tp~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) ->
datetime_iso8601(calendar:now_to_universal_time(Timestamp), MicroSeconds).
% ISO 8601 for date/time http://www.w3.org/TR/NOTE-datetime
datetime_iso8601({{DateYYYY, DateMM, DateDD},
{TimeHH, TimeMM, TimeSS}},
undefined) ->
[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),
[DateYYYY0, DateYYYY1, DateYYYY2, DateYYYY3, $-,
DateMM0, DateMM1, $-, DateDD0, DateDD1, $T,
TimeHH0, TimeHH1, $:, TimeMM0, TimeMM1, $:, TimeSS0, TimeSS1, $Z];
datetime_iso8601({{DateYYYY, DateMM, DateDD},
{TimeHH, TimeMM, TimeSS}},
MicroSeconds) ->
[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
?STACKTRACE(ErrorType, Error, ErrorStackTrace)
ErrorMessage = cloudi_string:
format_to_binary("formatter(~tp) ~tp ~tp~n~tp",
[Formatter, ErrorType, Error,
ErrorStackTrace]),
[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);
?STACKTRACE(ErrorType, Error, ErrorStackTrace)
ErrorMessage = cloudi_string:
format_to_binary("output(~tp) ~tp ~tp~n~tp",
[Output, ErrorType, Error,
ErrorStackTrace]),
[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(ModeInterface, 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, Process) of
{true, _} when ModeInterface =:= async ->
ok;
{_, FloodingWarning} ->
MetaData = metadata_get(),
LogMessage0 = if
is_list(Format), Args =:= undefined ->
unicode:characters_to_binary(Format);
true ->
log_message_safe(Format, Args)
end,
LogMessageN = if
FloodingWarning =:= undefined; ModeInterface =:= sync ->
LogMessage0;
is_binary(FloodingWarning) ->
[LogMessage0, FloodingWarning]
end,
if
ModeInterface =:= async ->
gen_server:cast(Process,
{Level, Timestamp, node(), self(),
Module, Line, Function, Arity,
MetaData, LogMessageN});
ModeInterface =:= sync ->
gen_server:call(Process,
{Level, Timestamp, node(), self(),
Module, Line, Function, Arity,
MetaData, LogMessageN},
infinity)
end
end.
flooding_logger_warning(SecondsRemaining, Delta, Remote) ->
Location = if
Remote =:= true ->
"remotely";
Remote =:= false ->
"locally"
end,
cloudi_string:format_to_binary("~n"
"... (~w logged/second async stopped process from~n"
" logging ~s for ~.2f seconds)",
[1000000 div Delta, Location, SecondsRemaining]).
% determine if a single process has sent too many logging messages
flooding_logger(Timestamp1, Process) ->
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),
{Delta, Remote} = if
is_tuple(Process) ->
{?LOGGER_FLOODING_DELTA_REMOTE, true};
true ->
{?LOGGER_FLOODING_DELTA_LOCAL, false}
end,
if
(MicroSecondsElapsed > ?LOGGER_FLOODING_INTERVAL_MAX) orelse
(MicroSecondsElapsed < 0) ->
erlang:put(?LOGGER_FLOODING_PDICT_KEY,
{Timestamp1, 1, false}),
{false, undefined};
(Flooding =:= false) andalso
(MicroSecondsElapsed > ?LOGGER_FLOODING_INTERVAL_MIN) andalso
(MicroSecondsElapsed div Count1 < Delta) ->
erlang:put(?LOGGER_FLOODING_PDICT_KEY,
{Timestamp0, Count1, true}),
SecondsRemaining = (?LOGGER_FLOODING_INTERVAL_MAX -
MicroSecondsElapsed) / 1000000,
{false,
flooding_logger_warning(SecondsRemaining, Delta, Remote)};
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) ->
log_message_internal_t0(LevelCheck, undefined,
Line, Function, Arity, Format, Args, State).
log_message_internal_t0(off, _, _, _, _, _, _, State) ->
{ok, State};
log_message_internal_t0(LevelCheck, TimestampOld,
Line, Function, Arity, Format, Args,
#state{level = Level,
destination = Destination,
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),
Timestamp = if
TimestampOld =:= undefined ->
cloudi_timestamp:timestamp();
tuple_size(TimestampOld) == 3 ->
TimestampOld
end,
if
Destination =:= ?MODULE ->
log_message_internal(sync,
LevelCheck, Timestamp, Node, Self,
?MODULE, Line, Function, Arity,
[], LogMessage, State);
true ->
ok = gen_server:cast(Destination,
{LevelCheck, Timestamp, Node, Self,
?MODULE, Line, Function, Arity,
[], LogMessage}),
{ok, State}
end;
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),
Timestamp = cloudi_timestamp:timestamp(),
gen_server:cast(Destination,
{LevelCheck, Timestamp, Node, Self,
?MODULE, Line, Function, Arity,
[], LogMessage});
false ->
ok
end.
log_message_internal(async, _, _, _, _, _, _, _, _, _, _,
#state{mode = overload} = State) ->
{ok, State};
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) ->
true = (Level =:= fatal) orelse (Level =:= error) orelse
(Level =:= warn) orelse (Level =:= info) orelse
(Level =:= debug) orelse (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, StateNew} = case log_level_allowed(MainLevel, Level) of
true ->
ok = log_stdout(Message, StdoutPort),
log_file(Level, Message, State);
false ->
{ok, State}
end,
case log_level_allowed(SyslogLevel, Level) of
true ->
ok = log_syslog(Level, Timestamp, Message, StateNew);
false ->
ok
end,
ok = aspects_log(AspectsLogAfter,
Level, Timestamp, Node, Pid,
Module, Line, Function, Arity,
MetaData, LogMessage),
{FileResult, StateNew}.
-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: ~tp ~tp",
[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(Level, Message,
#state{file_path = FilePath,
fd = FdOld,
inode = InodeOld,
error_read_count = ErrorReadCount,
error_read_types = ErrorReadTypes} = State) ->
case file:read_file_info(FilePath, [raw]) of
{ok, #file_info{inode = CurrentInode}} ->
if
CurrentInode == InodeOld ->
log_file_write(Level, Message, State);
true ->
_ = file:close(FdOld),
case log_file_reopen(FilePath, CurrentInode, State) of
{ok, StateNew} ->
log_file_write(Level, Message, 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, StateNew} ->
log_file_write(Level, Message, StateNew);
{error, _} ->
ErrorReadTypesNew = log_error_type(ErrorReadTypes, enoent),
{ok,
State#state{fd = undefined,
inode = undefined,
error_read_count = ErrorReadCount + 1,
error_read_types = ErrorReadTypesNew}}
end;
{error, Reason} ->
_ = file:close(FdOld),
true = is_atom(Reason),
ErrorReadTypesNew = log_error_type(ErrorReadTypes, Reason),
{ok,
State#state{fd = undefined,
inode = undefined,
error_read_count = ErrorReadCount + 1,
error_read_types = ErrorReadTypesNew}}
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_file_write(Level, Message,
#state{fd = Fd,
file_counts = FileCounts,
error_write_count = ErrorWriteCount,
error_write_types = ErrorWriteTypes,
error_sync_count = ErrorSyncCount,
error_sync_types = ErrorSyncTypes} = State) ->
case file:write(Fd, Message) of
ok ->
case file:datasync(Fd) of
ok ->
FileCountsNew = maps:update_with(Level, fun(Count) ->
Count + 1
end, 1, FileCounts),
{ok, State#state{file_counts = FileCountsNew}};
{error, Reason} ->
true = is_atom(Reason),
ErrorSyncTypesNew = log_error_type(ErrorSyncTypes, Reason),
{ok,
State#state{error_sync_count = ErrorSyncCount + 1,
error_sync_types = ErrorSyncTypesNew}}
end;
{error, Reason} ->
true = is_atom(Reason),
ErrorWriteTypesNew = log_error_type(ErrorWriteTypes, Reason),
{ok,
State#state{error_write_count = ErrorWriteCount + 1,
error_write_types = ErrorWriteTypesNew}}
end.
log_error_type([], Reason) ->
[Reason];
log_error_type([Reason | _] = L, Reason) ->
L;
log_error_type([ReasonOld | L], Reason) ->
[ReasonOld | log_error_type(L, Reason)].
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_T0_INFO("redirecting log output to ~ts",
[NodeLogger], State) of
{ok, StateNext} ->
case load_interface_module(Level, Mode, DestinationNew) of
{ok, Binary} ->
?LOG_T1_INFO("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(Timestamp,
#state{level = Level,
queue_pending = 0,
queue_mode_async = QueueModeAsync,
queue_mode_sync = QueueModeSync,
queue_mode_overload = QueueModeOverload,
mode = ModeOld,
destination = Destination,
logger_self = Self} = State) ->
{message_queue_len,
QueueLength} = erlang:process_info(Self, message_queue_len),
QueueModeOverloadMin = QueueModeOverload - ?LOGGER_MODE_OVERLOAD_OFFSET,
ModeNew = if
ModeOld =:= async,
QueueLength >= QueueModeSync ->
sync;
ModeOld =:= sync ->
if
QueueLength =< QueueModeAsync ->
async;
QueueLength >= QueueModeOverload ->
overload;
true ->
sync
end;
ModeOld =:= overload,
QueueLength =< QueueModeOverloadMin ->
sync;
true ->
ModeOld
end,
QueuePending = if
ModeNew =:= overload ->
QueueLength - QueueModeOverloadMin;
ModeNew =:= sync ->
if
QueueLength =< QueueModeOverloadMin div 2 ->
QueueLength - QueueModeAsync;
QueueLength < QueueModeOverloadMin ->
QueueModeOverloadMin div 10;
QueueLength >= QueueModeOverloadMin ->
?LOGGER_MODE_OVERLOAD_OFFSET div 10
end;
true ->
0
end,
if
ModeNew /= ModeOld ->
case load_interface_module(Level, ModeNew, Destination) of
{ok, Binary} ->
log_mode_changed(ModeNew, ModeOld, Timestamp,
State#state{interface_module = Binary,
queue_pending = QueuePending,
mode = ModeNew});
{error, _} = Error ->
Error
end;
true ->
{ok, State#state{queue_pending = QueuePending}}
end;
log_mode_check(_, #state{queue_pending = QueuePending} = State) ->
true = QueuePending > 0,
{ok, State#state{queue_pending = QueuePending - 1}}.
log_mode_changed(sync, async, Timestamp, State) ->
{ok, State#state{mode_sync_start = cloudi_timestamp:native_monotonic(),
mode_sync_start_event = timestamp_iso8601(Timestamp),
mode_sync_end = undefined,
mode_sync_end_event = undefined,
mode_sync_total = undefined}};
log_mode_changed(async, sync, Timestamp,
#state{mode_sync_start = SyncStart} = State) ->
SyncEnd = cloudi_timestamp:native_monotonic(),
SyncEndEvent = timestamp_iso8601(Timestamp),
SyncNanoSeconds = cloudi_timestamp:
convert(SyncEnd - SyncStart,
native, nanosecond),
SyncTotal = cloudi_timestamp:
nanoseconds_to_string(SyncNanoSeconds),
{ok, State#state{mode_sync_end = SyncEnd,
mode_sync_end_event = SyncEndEvent,
mode_sync_total = SyncTotal}};
log_mode_changed(overload, sync, Timestamp, State) ->
{ok, State#state{mode_overload_start = cloudi_timestamp:native_monotonic(),
mode_overload_start_event = timestamp_iso8601(Timestamp),
mode_overload_end = undefined,
mode_overload_end_event = undefined,
mode_overload_total = undefined}};
log_mode_changed(sync, overload, Timestamp,
#state{mode_overload_start = OverloadStart} = State) ->
OverloadEnd = cloudi_timestamp:native_monotonic(),
OverloadEndEvent = timestamp_iso8601(Timestamp),
OverloadNanoSeconds = cloudi_timestamp:
convert(OverloadEnd - OverloadStart,
native, nanosecond),
OverloadTotal = cloudi_timestamp:
nanoseconds_to_string(OverloadNanoSeconds),
StateNew = State#state{mode_overload_end = OverloadEnd,
mode_overload_end_event = OverloadEndEvent,
mode_overload_total = OverloadTotal},
?LOG_T0_ERROR("logging overload occurred for ~s",
[OverloadTotal], StateNew);
log_mode_changed(_, _, _, State) ->
{ok, State}.
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}.
interface(Level, overload, Destination) ->
cloudi_string:format(?INTERFACE_MODULE_OVERLOAD_CODE(Level),
?INTERFACE_MODULE_OVERLOAD_ARGS(Level,
Destination));
interface(Level, ModeInterface, Destination)
when ModeInterface =:= async; ModeInterface =:= sync ->
cloudi_string:format(?INTERFACE_MODULE_NORMAL_CODE(Level),
?INTERFACE_MODULE_NORMAL_ARGS(Level,
ModeInterface,
Destination)).
load_interface_module(undefined, _, _) ->
{error, logging_level_undefined};
load_interface_module(Level, Mode, Destination) when is_atom(Level) ->
ModuleText = interface(Level, Mode, Destination),
{ok, Module, Binary} = merl:compile(merl:quote(ModuleText)),
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(undefined, _, State) ->
{ok, State#state{syslog = undefined,
syslog_level = undefined}};
syslog_open(#config_logging_syslog{identity = SyslogIdentity,
facility = SyslogFacility,
level = SyslogLevel,
transport = SyslogTransport,
transport_options = SyslogTransportOptions,
protocol = SyslogProtocol,
path = SyslogPath,
host = SyslogHost,
port = SyslogPort},
LogError, State) ->
Options = [{app_name, SyslogIdentity},
{facility, SyslogFacility},
{transport, SyslogTransport},
{transport_options, SyslogTransportOptions},
{protocol, SyslogProtocol},
{utf8, true},
{path, SyslogPath},
{host, SyslogHost},
{port, SyslogPort},
{timeout, 5000}],
case syslog_socket:start_monitor(Options) of
{ok, Syslog} ->
{ok, State#state{syslog = Syslog,
syslog_level = SyslogLevel}};
{error, Reason} = Error ->
if
LogError =:= true ->
?LOG_T0_ERROR("syslog error: ~tp", [Reason],
State#state{syslog = undefined,
syslog_level = undefined});
LogError =:= false ->
{Error, State}
end
end.
syslog_close(undefined) ->
ok;
syslog_close(Syslog) when is_pid(Syslog) ->
syslog_socket:stop_monitor(Syslog). % asynchronous stop
filepath_exists(FilePath) ->
case file:open(FilePath, [raw, append]) of
{ok, Fd} ->
file:close(Fd);
{error, _} = Error ->
Error
end.
-ifdef(ERLANG_OTP_VERSION_20_FEATURES).
time_offset_nanoseconds() ->
erlang:time_offset(nanosecond).
time_offset_to_nanoseconds(TimeOffset) ->
erlang:convert_time_unit(TimeOffset, native, nanosecond).
-else.
time_offset_nanoseconds() ->
erlang:time_offset(nano_seconds).
time_offset_to_nanoseconds(TimeOffset) ->
erlang:convert_time_unit(TimeOffset, native, nano_seconds).
-endif.
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.
nanoseconds_to_seconds_change_string(NanoSecondsOld, NanoSecondsNew) ->
{Sign, NanoSecondsChange} = if
NanoSecondsNew >= NanoSecondsOld ->
{$+, NanoSecondsNew - NanoSecondsOld};
true ->
{$-, NanoSecondsOld - NanoSecondsNew}
end,
Str = int_to_dec_list(NanoSecondsChange, 9, $0),
[NanoSeconds8, NanoSeconds7, NanoSeconds6,
NanoSeconds5, NanoSeconds4, NanoSeconds3,
NanoSeconds2, NanoSeconds1, NanoSeconds0 | Seconds] = lists:reverse(Str),
SecondsFractionStr = [$.,
NanoSeconds0, NanoSeconds1, NanoSeconds2,
NanoSeconds3, NanoSeconds4, NanoSeconds5,
NanoSeconds6, NanoSeconds7, NanoSeconds8, $ ,
$s, $e, $c, $o, $n, $d, $s],
if
Seconds == [] ->
[Sign, $0 | SecondsFractionStr];
true ->
[Sign | lists:reverse(Seconds, SecondsFractionStr)]
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, Count, _) when Count =< 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 accum(L :: list({any(),
fun((any(), #state{}) ->
{ok, #state{}} | {{error, any()}, #state{}})}),
State :: #state{}) ->
{ok, #state{}} | {{error, any()}, #state{}}.
accum([], State) ->
{ok, State};
accum([{Value, F} | L], State) ->
case F(Value, State) of
{ok, StateNew} ->
accum(L, StateNew);
{{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: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({any(), 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
is_list(MetaData0) ->
MetaData0;
is_map(MetaData0) ->
maps:to_list(MetaData0)
end,
MetaData2 = if
Function =:= undefined ->
MetaData1;
true ->
[{function, Function} | MetaData1]
end,
MetaDataN = [{module, Module},
{line, Line},
{node, Node},
{pid, erlang:pid_to_list(Pid)} | MetaData2],
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}.