|
|
- -module(eRum).
-
-
- -include("rumDef.hrl").
- -include("rumCom.hrl").
-
- -compile(inline).
- -compile({inline_size, 150}).
-
- %% API
- -export([
- %% start stop
- start/0
- , stop/0
-
- %% log and log param
- , dispatchLog/12
- , doLogImpl/13
- , safeFormat/3
- , unsafeFormat/2
- , getMd/0
- , setMd/1
- , getLogLevel/1
- , getLogLevel/2
- , setLogLevel/2
- , setLogLevel/3
- , setLogLevel/4
- , getLogLevels/1
- , upLogLevelCfg/1
- , setLogHwm/2
- , setLogHwm/3
- , setLogHwm/4
- , rotateHandler/1
- , rotateHandler/2
- , rotateSink/1
- , rotateAll/0
-
- %% stack parse
- , parseStack/1
- , parseStack/3
-
- %% trace
- , trace/2
- , trace/3
- , traceFile/2
- , trace_file/3
- , trace_file/4
- , trace_console/1
- , trace_console/2
- , install_trace/2
- , install_trace/3
- , remove_trace/1
- , trace_state/3
- , trace_func/3
- , list_all_sinks/0
- , clear_all_traces/0
- , clear_trace_by_destination/1
- , stop_trace/1
- , stop_trace/3
- , status/0
- ]).
-
- -record(trace_func_state_v1, {
- pid :: undefined | pid(),
- level :: rumAtomLevel(),
- count :: infinity | pos_integer(),
- format_string :: string(),
- timeout :: infinity | pos_integer(),
- started = os:timestamp() :: erlang:timestamp() %% use os:timestamp for compatability
- }).
-
- start() ->
- application:ensure_all_started(eRum).
-
- stop() ->
- application:stop(eRum).
-
- -spec dispatchLog(atom(), rumAtomLevel(), pid(), node(), atom(), atom(), integer(), list(), string(), list() | none, pos_integer(), safe | unsafe) -> ok | {error, lager_not_running} | {error, {sink_not_configured, atom()}}.
- %% this is the same check that the parse transform bakes into the module at compile time see rumTransform (lines 173-216)
- dispatchLog(Sink, Severity, Pid, Node, Module, Function, Line, Metadata, Format, Args, Size, Safety) ->
- case ?eRumCfg:get(Sink) band Severity /= 0 of
- true ->
- doLogImpl(Severity, Pid, Node, Module, Function, Line, Metadata, Format, Args, Severity, Size, Sink, Safety);
- _ ->
- ok
- end.
-
- doLogImpl(Severity, Pid, Node, Module, Function, Line, Metadata, Format, Args, Severity, Size, Sink, Safety) ->
- TraceFilters = rumConfig:ptGet({Sink, trace}, []),
- Destinations = ?IIF(TraceFilters /= [], rumUtil:check_traces(Metadata, Severity, TraceFilters, []), []),
-
- MsgStr = ?IIF(Args /= [] andalso Args /= undefined, ?IIF(Safety == safe, safeFormat(Format, [Args], [{charsLimit, Size}]), unsafeFormat(Format, [Args])), Format),
- NowMs = rumTime:nowMs(),
- NowStr = rumUtil:msToBinStr(NowMs),
- RumMsg = #rumMsg{severity = Severity, pid = Pid, node = Node, module = Module, function = Function, line = Line, metadata = Metadata, datetime = NowStr, timestamp = NowMs, message = MsgStr, destinations = Destinations},
-
- case rumConfig:ptGet({Sink, async}, false) of
- true ->
- gen_emm:info_notify(Sink, {mWriteLog, RumMsg});
- false ->
- gen_emm:call_notify(Sink, {mWriteLog, RumMsg})
- end,
- case whereis(?RumTrackSink) of
- undefined ->
- ok;
- TraceSinkPid ->
- gen_emm:info_notify(TraceSinkPid, {mWriteLog, RumMsg})
- end.
-
- %% @doc Get lager metadata for current process
- -spec getMd() -> [{atom(), any()}].
- getMd() ->
- case erlang:get(?PdMdKey) of
- undefined -> [];
- MD -> MD
- end.
-
- %% @doc Set lager metadata for current process.
- %% Will badarg if you don't supply a list of {key, value} tuples keyed by atoms.
- -spec setMd([{atom(), any()}, ...]) -> ok.
- setMd(NewMD) when is_list(NewMD) ->
- %% make sure its actually a real proplist
- case lists:all(
- fun({Key, _Value}) when is_atom(Key) -> true;
- (_) -> false
- end, NewMD) of
- true ->
- erlang:put(?PdMdKey, NewMD),
- ok;
- _ ->
- erlang:error(badarg)
- end;
- setMd(_) ->
- erlang:error(badarg).
-
- %% @doc Set the loglevel for a particular backend.
- setLogLevel(Handler, Level) when is_atom(Level) ->
- setLogLevel(?RumDefSink, Handler, undefined, Level).
-
- %% @doc Set the loglevel for a particular backend that has multiple identifiers
- %% (eg. the file backend).
- setLogLevel(Handler, Ident, Level) when is_atom(Level) ->
- setLogLevel(?RumDefSink, Handler, Ident, Level).
-
- %% @doc Set the loglevel for a particular sink's backend that potentially has
- %% multiple identifiers. (Use `undefined' if it doesn't have any.)
- setLogLevel(Sink, Handler, Ident, Level) when is_atom(Level) ->
- HandlerArg =
- case Ident of
- undefined -> Handler;
- _ -> {Handler, Ident}
- end,
- Reply = gen_emm:call(Sink, HandlerArg, {mSetLogLevel, Level}, infinity),
- upLogLevelCfg(Sink),
- Reply.
-
-
- %% @doc Get the loglevel for a particular backend on the default sink. In the case that the backend has multiple identifiers, the lowest is returned.
- getLogLevel(Handler) ->
- getLogLevel(?RumDefSink, Handler).
-
- %% @doc Get the loglevel for a particular sink's backend. In the case that the backend
- %% has multiple identifiers, the lowest is returned.
- getLogLevel(Sink, Handler) ->
- case gen_emm:call(Sink, Handler, mGetLogLevel, infinity) of
- Mask when is_integer(Mask) ->
- case rumUtil:maskToLevels(Mask) of
- [] -> none;
- Levels -> hd(Levels)
- end;
- Y -> Y
- end.
-
- getLogLevels(Sink) ->
- [gen_emm:call(Sink, Handler, mGetLogLevel, infinity) || Handler <- gen_emm:which_epm(Sink)].
-
- %% @doc Set the loghwm for the default sink.
- setLogHwm(Handler, Hwm) when is_integer(Hwm) ->
- setLogHwm(?RumDefSink, Handler, Hwm).
-
- %% @doc Set the loghwm for a particular backend.
- setLogHwm(Sink, Handler, Hwm) when is_integer(Hwm) ->
- gen_emm:call(Sink, Handler, {mSetLogHwm, Hwm}, infinity).
-
- %% @doc Set the loghwm (log high water mark) for file backends with multiple identifiers
- setLogHwm(Sink, Handler, Ident, Hwm) when is_integer(Hwm) ->
- gen_emm:call(Sink, {Handler, Ident}, {mSetLogHwm, Hwm}, infinity).
-
- %% @doc recalculate min log level
- upLogLevelCfg(error_logger) ->
- %% Not a sink under our control, part of the Erlang logging
- %% utility that error_logger_lager_h attaches to
- true;
- upLogLevelCfg(Sink) ->
- Traces = rumConfig:ptGet({Sink, trace}, []),
- AllLogLevel = allLogLevel(getLogLevels(Sink), 0),
- case Traces /= [] of
- true ->
- ets:insert(?eRumEts, {Sink, 16#ff}),
- AllSinks = ets:tab2list(?eRumEts),
- rumKvsToBeam:load(?eRumCfg, AllSinks);
- _ ->
- ets:insert(?eRumEts, {Sink, AllLogLevel}),
- AllSinks = ets:tab2list(?eRumEts),
- rumKvsToBeam:load(?eRumCfg, AllSinks)
- end.
-
- allLogLevel([], Acc) ->
- Acc;
- allLogLevel([OneLv | Levels], Acc) ->
- allLogLevel(Levels, OneLv bor Acc).
-
- rotateSink(Sink) ->
- Handlers = rumConfig:ptGet(handlers, []),
- RotateHandlers = lists:filtermap(
- fun({Handler, _, S}) when S == Sink -> {true, {Handler, Sink}};
- (_) -> false
- end,
- Handlers),
- rotateHandlers(RotateHandlers).
-
- rotateAll() ->
- rotateHandlers(lists:map(fun({H, _, S}) -> {H, S} end,
- rumConfig:ptGet(handlers, []))).
-
-
- rotateHandlers(Handlers) ->
- [rotateHandler(Handler, Sink) || {Handler, Sink} <- Handlers].
-
-
- rotateHandler(Handler) ->
- Handlers = rumConfig:ptGet(handlers, []),
- case lists:keyfind(Handler, 1, Handlers) of
- {Handler, _, Sink} -> rotateHandler(Handler, Sink);
- false -> ok
- end.
-
- rotateHandler(Handler, Sink) ->
- gen_emm:call(Sink, Handler, mRotate, ?RumRotateTimeout).
-
- %% @doc Print stacktrace in human readable form
- parseStack(Stacktrace) ->
- <<
- begin
- case Location of
- [] ->
- <<" ", (atom_to_binary(Mod, utf8))/binary, ":", (atom_to_binary(Func, utf8))/binary, "(", (eFmt:formatBin("~w", [Arity]))/binary, ")\n">>;
- [{file, File}, {line, Line}] ->
- <<" ", (atom_to_binary(Mod, utf8))/binary, ":", (atom_to_binary(Func, utf8))/binary, "/", (integer_to_binary(Arity))/binary, "(", (unicode:characters_to_binary(File))/binary, ":", (integer_to_binary(Line))/binary, ")\n">>;
- _ ->
- <<" ", (atom_to_binary(Mod, utf8))/binary, ":", (atom_to_binary(Func, utf8))/binary, "(", (eFmt:formatBin("~w", [Arity]))/binary, ")", (eFmt:formatBin("~w", [Location]))/binary, "\n">>
- end
- end || {Mod, Func, Arity, Location} <- Stacktrace
- >>.
-
- parseStack(Class, Reason, Stacktrace) ->
- eFmt:formatBin(<<"~n Class:~s~n Reason:~p~n Stacktrace:~s">>, [Class, Reason, parseStack(Stacktrace)]).
-
- trace(BkdMod, Filter) ->
- trace(BkdMod, Filter, debug).
-
- trace({rumBkdFile, File}, Filter, Level) ->
- trace_file(File, Filter, Level);
- trace(Backend, Filter, Level) ->
- case validateTraceFilters(Filter, Level, Backend) of
- {Sink, {ok, Trace}} ->
- add_trace_to_loglevel_config(Trace, Sink),
- {ok, {Backend, Filter, Level}};
- {_Sink, Error} ->
- Error
- end.
-
- traceFile(File, Filter) ->
- trace_file(File, Filter, debug, []).
-
- trace_file(File, Filter, Level) when is_atom(Level) ->
- trace_file(File, Filter, Level, []);
-
- trace_file(File, Filter, Options) when is_list(Options) ->
- trace_file(File, Filter, debug, Options).
-
- trace_file(File, Filter, Level, Options) ->
- FileName = rumUtil:parsePath(File),
- case validateTraceFilters(Filter, Level, {rumBkdFile, FileName}) of
- {Sink, {ok, Trace}} ->
- Handlers = rumConfig:ptGet(handlers, []),
- %% check if this file backend is already installed
- Res =
- case rumUtil:find_file(FileName, Handlers) of
- false ->
- %% install the handler
- LogFileConfig =
- lists:keystore(level, 1,
- lists:keystore(file, 1,
- Options,
- {file, FileName}),
- {level, none}),
- HandlerInfo =
- eRum_app:startHandler(Sink, {rumBkdFile, FileName}, LogFileConfig),
- rumConfig:ptSet(handlers, [HandlerInfo | Handlers]),
- {ok, installed};
- {_Watcher, _Handler, Sink} ->
- {ok, exists};
- {_Watcher, _Handler, _OtherSink} ->
- {error, file_in_use}
- end,
- case Res of
- {ok, _} ->
- add_trace_to_loglevel_config(Trace, Sink),
- {ok, {{rumBkdFile, FileName}, Filter, Level}};
- {error, _} = E ->
- E
- end;
- {_Sink, Error} ->
- Error
- end.
-
- trace_console(Filter) ->
- trace_console(Filter, debug).
-
- trace_console(Filter, Level) ->
- trace(rumBkdConsole, Filter, Level).
-
- stop_trace(Backend, Filter, Level) ->
- case validateTraceFilters(Filter, Level, Backend) of
- {Sink, {ok, Trace}} ->
- stop_trace_int(Trace, Sink);
- {_Sink, Error} ->
- Error
- end.
-
- stop_trace({Backend, Filter, Level}) ->
- stop_trace(Backend, Filter, Level).
-
-
- validateTraceFilters(Filters, Level, Backend) ->
- Sink = proplists:get_value(sink, Filters, ?RumDefSink),
- {Sink,
- rumUtil:validate_trace({
- proplists:delete(sink, Filters),
- Level,
- Backend
- })
- }.
-
- %% Important: validate_trace_filters orders the arguments of
- %% trace tuples differently than the way outside callers have
- %% the trace tuple.
- %%
- %% That is to say, outside they are represented as
- %% `{Backend, Filter, Level}'
- %%
- %% and when they come back from validation, they're
- %% `{Filter, Level, Backend}'
- stop_trace_int({_Filter, _Level, Backend} = Trace, Sink) ->
- Traces = rumConfig:ptGet({Sink, trace}, []),
- NewTraces = lists:delete(Trace, Traces),
- _ = rumUtil:trace_filter([element(1, T) || T <- NewTraces]),
- %MinLevel = minimum_loglevel(get_loglevels() ++ get_trace_levels(NewTraces)),
- rumConfig:ptSet({Sink, trace}, NewTraces),
- eRum:upLogLevelCfg(Sink),
-
- case getLogLevel(Sink, Backend) of
- none ->
- %% check no other traces point here
- case lists:keyfind(Backend, 3, NewTraces) of
- false ->
- gen_emm:delEpm(Sink, Backend, []),
- rumConfig:ptSet(handlers,
- lists:keydelete(Backend, 1,
- rumConfig:ptGet(handlers, [])));
- _ ->
- ok
- end;
- _ ->
- ok
- end,
- ok.
-
- %% @doc installs a lager trace handler into the target process (using sys:install) at the specified level.
- -spec install_trace(pid(), rumAtomLevel()) -> ok.
- install_trace(Pid, Level) ->
- install_trace(Pid, Level, []).
-
- -spec install_trace(pid(), rumAtomLevel(), [{count, infinity | pos_integer()} | {format_string, string()} | {timeout, timeout()}]) -> ok.
- install_trace(Pid, Level, Options) ->
- sys:install(Pid, {fun ?MODULE:trace_func/3, trace_state(Pid, Level, Options)}).
-
- %% @doc remove a previously installed lager trace handler from the target process.
- -spec remove_trace(pid()) -> ok.
- remove_trace(Pid) ->
- sys:remove(Pid, fun ?MODULE:trace_func/3).
-
- list_all_sinks() ->
- sets:to_list(
- lists:foldl(fun({_Watcher, _Handler, Sink}, Set) ->
- sets:add_element(Sink, Set)
- end,
- sets:new(),
- rumConfig:ptGet(handlers, []))).
-
- clear_traces_by_sink(Sinks) ->
- lists:foreach(
- fun(S) ->
- rumConfig:ptSet({S, trace}, []),
- eRum:upLogLevelCfg(S)
- end,
- Sinks).
-
-
- clear_trace_by_destination(ID) ->
- Sinks = lists:sort(list_all_sinks()),
- Traces = find_traces(Sinks),
- [stop_trace_int({Filter, Level, Destination}, Sink) || {Sink, {Filter, Level, Destination}} <- Traces, Destination == ID].
-
-
- clear_all_traces() ->
- Handlers = rumConfig:ptGet(handlers, []),
- clear_traces_by_sink(list_all_sinks()),
- _ = rumUtil:trace_filter(none),
- rumConfig:ptSet(handlers,
- lists:filter(
- fun({Handler, _Watcher, Sink}) ->
- case getLogLevel(Sink, Handler) of
- none ->
- gen_emm:delEpm(Sink, Handler, []),
- false;
- _ ->
- true
- end
- end, Handlers)).
-
- find_traces(Sinks) ->
- lists:foldl(fun(S, Acc) ->
- Traces = rumConfig:ptGet({S, trace}, []),
- Acc ++ lists:map(fun(T) -> {S, T} end, Traces)
- end,
- [],
- Sinks).
-
- status() ->
- Handlers = rumConfig:ptGet(handlers, []),
- Sinks = lists:sort(list_all_sinks()),
- Traces = find_traces(Sinks),
- TraceCount = case length(Traces) of
- 0 -> 1;
- N -> N
- end,
- Status = ["Lager status:\n",
- [begin
- Level = getLogLevel(Sink, Handler),
- get_sink_handler_status(Sink, Handler, Level)
- end || {Handler, _Watcher, Sink} <- lists:sort(fun({_, _, S1},
- {_, _, S2}) -> S1 =< S2 end,
- Handlers)],
- "Active Traces:\n",
- [begin
- LevelName =
- case rumUtil:maskToLevels(Level) of
- [] -> none;
- Levels -> hd(Levels)
- end,
-
- io_lib:format("Tracing messages matching ~p (sink ~s) at level ~p to ~p\n",
- [Filter, Sink, LevelName, Destination])
- end || {Sink, {Filter, Level, Destination}} <- Traces],
- [
- "Tracing Reductions:\n",
- case ?RumDefTracer:info('query') of
- {null, false} -> "";
- Query -> io_lib:format("~p~n", [Query])
- end
- ],
- [
- "Tracing Statistics:\n ",
- [begin
- [" ", atom_to_list(Table), ": ",
- integer_to_list(?RumDefTracer:info(Table) div TraceCount),
- "\n"]
- end || Table <- [input, output, filter]]
- ]],
- io:put_chars(Status).
-
- get_sink_handler_status(Sink, Handler, Level) ->
- case Handler of
- {rumBkdFile, File} ->
- io_lib:format("File ~ts (~s) at level ~p\n", [File, Sink, Level]);
- rumBkdConsole ->
- io_lib:format("Console (~s) at level ~p\n", [Sink, Level]);
- _ ->
- []
- end.
-
- %% @private
- add_trace_to_loglevel_config(Trace, Sink) ->
- Traces = rumConfig:ptGet({Sink, trace}, []),
- case lists:member(Trace, Traces) of
- false ->
- NewTraces = [Trace | Traces],
- _ = rumUtil:trace_filter([element(1, T) || T <- NewTraces]),
- rumConfig:ptSet({Sink, trace}, [Trace | Traces]),
- eRum:upLogLevelCfg(Sink);
- _ ->
- ok
- end.
-
- %% @doc Print the format string `Fmt' with `Args' safely with a size
- %% limit of `Limit'. If the format string is invalid, or not enough
- %% arguments are supplied 'FORMAT ERROR' is printed with the offending
- %% arguments. The caller is NOT crashed.
-
- unsafeFormat(Fmt, Args) ->
- try io_lib:format(Fmt, Args)
- catch
- _:_ -> io_lib:format("FORMAT ERROR: ~p ~p", [Fmt, Args])
- end.
-
- safeFormat(Fmt, Args, Limit) ->
- try eFmt:formatBin(Fmt, Args, [{charsLimit, Limit}])
- catch
- _:_ -> eFmt:formatBin(<<"FORMAT ERROR: ~p ~p">>, [Fmt, Args], [{charsLimit, Limit}])
- end.
-
- %% @private Print the format string `Fmt' with `Args' without a size limit.
- %% This is unsafe because the output of this function is unbounded.
- %%
- %% Log messages with unbounded size will kill your application dead as
- %% OTP mechanisms stuggle to cope with them. So this function is
- %% intended <b>only</b> for messages which have a reasonable bounded
- %% size before they're formatted.
- %%
- %% If the format string is invalid or not enough arguments are
- %% supplied a 'FORMAT ERROR' message is printed instead with the
- %% offending arguments. The caller is NOT crashed.
-
- %% @private
- trace_func(#trace_func_state_v1{pid = Pid, level = Level, format_string = Fmt} = FuncState, Event, ProcState) ->
- _ = eRum:log(Level, Pid, Fmt, [Event, ProcState]),
- check_timeout(decrement_count(FuncState)).
-
- %% @private
- trace_state(Pid, Level, Options) ->
- #trace_func_state_v1{pid = Pid,
- level = Level,
- count = proplists:get_value(count, Options, infinity),
- timeout = proplists:get_value(timeout, Options, infinity),
- format_string = proplists:get_value(format_string, Options, "TRACE ~p ~p")}.
-
- decrement_count(#trace_func_state_v1{count = infinity} = FuncState) ->
- FuncState;
- decrement_count(#trace_func_state_v1{count = 1}) ->
- %% hit the counter limit
- done;
- decrement_count(#trace_func_state_v1{count = Count} = FuncState) ->
- FuncState#trace_func_state_v1{count = Count - 1}.
-
- check_timeout(#trace_func_state_v1{timeout = infinity} = FuncState) ->
- FuncState;
- check_timeout(#trace_func_state_v1{timeout = Timeout, started = Started} = FuncState) ->
- case (timer:now_diff(os:timestamp(), Started) / 1000) > Timeout of
- true ->
- done;
- false ->
- FuncState
- end.
|