%% @copyright 2014-2016 Takeru Ohta %% %% @doc The location where log message issued %% %% == EXAMPLE == %%
%% > Location = logi_location:new(lists, filter, 10).
%% > logi_location:to_map(Location).
%% #{application => stdlib,
%%   function => filter,
%%   line => 10,
%%   module => lists,
%%   process => <0.91.0>}
%% 
%% %% {@link guess_location/0} returns the current location. %%
%% > Location = logi_location:guess_location(). % If `logi_transform' is not used, a warning will be emitted.
%% =WARNING REPORT==== 19-Oct-2015::14:02:26 ===
%%    pid: <0.91.0>
%%    module: erl_eval
%%    function: do_apply
%%    line: 673
%%    msg: "A deprecated function 'logi_location:guess_location/0' is called. Please use the `{parse_transform, logi_transform}' compiler option."
%%
%% > logi_location:to_map(Location).
%% #{application => stdlib,
%%   function => do_apply,
%%   line => 673,
%%   module => erl_eval,
%%   process => <0.91.0>}
%% 
%% @end -module(logi_location). -deprecated({guess_location, 0}). %%---------------------------------------------------------------------------------------------------------------------- %% Exported API %%---------------------------------------------------------------------------------------------------------------------- -export([new/3, new/5, unsafe_new/5]). -export([is_location/1]). -export([to_map/1, from_map/1]). -export([guess_location/0]). -export([guess_application/1]). -export([get_process/1, get_application/1, get_module/1, get_function/1, get_line/1]). -export_type([location/0]). -export_type([map_form/0]). -export_type([application/0, line/0, line_or_anno/0]). %%---------------------------------------------------------------------------------------------------------------------- %% Macros & Records & Types %%---------------------------------------------------------------------------------------------------------------------- -define(LOCATION, ?MODULE). -record(?LOCATION, { process :: pid(), application :: application(), module :: module(), function :: atom(), line_or_anno :: line_or_anno() }). -opaque location() :: #?LOCATION{}. %% A log message issued location -type map_form() :: #{ process => pid(), application => application(), module => module(), function => atom(), line => line() }. %% The map representation of a location -type application() :: atom(). %% An application name -type line() :: pos_integer() | 0. %% A line number %% %% `0' means "Unknown Line" -type line_or_anno() :: line() | erl_anno:anno(). %% A line number or an erl_anno:anno() %% %% Starting from OTP 23, the abstract format uses annos instead of line numbers. %% This type absorbs the change. %% See: https://www.erlang.org/docs/23/apps/erts/absform.html %%---------------------------------------------------------------------------------------------------------------------- %% Exported Functions %%---------------------------------------------------------------------------------------------------------------------- %% @equiv new(self(), guess_application(Module), Module, Function, Line) -spec new(module(), atom(), line_or_anno()) -> location(). new(Module, Function, LineOrAnno) -> new(self(), guess_application(Module), Module, Function, LineOrAnno). %% @doc Creates a new location object -spec new(pid(), application(), module(), atom(), line_or_anno()) -> location(). new(Pid, Application, Module, Function, LineOrAnno) -> Args = [Pid, Application, Module, Function, LineOrAnno], _ = is_pid(Pid) orelse error(badarg, Args), _ = is_atom(Application) orelse error(badarg, Args), _ = is_atom(Module) orelse error(badarg, Args), _ = is_atom(Function) orelse error(badarg, Args), _ = (is_integer(LineOrAnno) andalso LineOrAnno >= 0) orelse erl_anno:is_anno(LineOrAnno) orelse error(badarg, Args), unsafe_new(Pid, Application, Module, Function, LineOrAnno). %% @doc Equivalent to {@link new/5} except omission of the arguments validation -spec unsafe_new(pid(), application(), module(), atom(), line_or_anno()) -> location(). unsafe_new(Pid, Application, Module, Function, LineOrAnno) -> #?LOCATION{ process = Pid, application = Application, module = Module, function = Function, line_or_anno = LineOrAnno }. %% @doc Returns `true' if `X' is a location object, `false' otherwise. -spec is_location(X :: (location() | term())) -> boolean(). is_location(X) -> is_record(X, ?LOCATION). %% @doc Creates a new location from `Map' %% %% Default Value: %% - process: `self()' %% - application: `guess_application(maps:get(module, Map))' %% - module: `undefined' %% - function: `undefined' %% - line: `0' -spec from_map(Map :: map_form()) -> location(). from_map(Map) -> _ = is_map(Map) orelse error(badarg, [Map]), Module = maps:get(module, Map, undefined), Application = case maps:find(application, Map) of error -> guess_application(Module); {ok, App} -> App end, new(maps:get(process, Map, self()), Application, Module, maps:get(function, Map, undefined), maps:get(line, Map, 0)). %% @doc Converts `Location' into a map form -spec to_map(Location :: location()) -> map_form(). to_map(L) -> #{ process => get_process(L), application => get_application(L), module => get_module(L), function => get_function(L), line => get_line(L) }. %% @doc Guesses the location where the function is called (parse transformation fallback) %% %% This function is too slow and provided for debugging/testing purposes only. %% %% @deprecated Please use the `{parse_transform, logi_transform}' compiler option %% which replaces the function call to a more efficient code. -spec guess_location() -> location(). guess_location() -> Location = case process_info(self(), current_stacktrace) of % NOTE: In the case of tail calls, this will return inaccurate result {current_stacktrace, Stack} -> case lists:dropwhile(fun ({M, _, _, _}) -> guess_application(M) =:= logi end, Stack) of [{Module, Function, _, Loc} | _] -> Line = proplists:get_value(line, Loc, 0), new(Module, Function, Line); _ -> new(undefined, undefined, 0) end; _ -> new(undefined, undefined, 0) end, _ = case application:get_env(logi, warn_no_parse_transform, true) of false -> ok; true -> Message = "A deprecated function 'logi_location:guess_location/0' is called. " "Please use the `{parse_transform, logi_transform}' compiler option.", case application:get_env(logi, warnings_as_errors, false) of true -> error(Message); false -> error_logger:warning_report( [ {pid, get_process(Location)}, {module, get_module(Location)}, {function, get_function(Location)}, {line, get_line(Location)}, {msg, Message} ]) end end, Location. %% @doc Guesses the application to which `Module' belongs -spec guess_application(module()) -> atom() | undefined. guess_application(Module) -> case application:get_application(Module) of {ok, App} -> App; undefined -> undefined end. %% @doc Gets the PID of `Location' -spec get_process(Location :: location()) -> pid(). get_process(#?LOCATION{process = Pid}) -> Pid. %% @doc Gets the application of `Location' -spec get_application(Location :: location()) -> atom(). get_application(#?LOCATION{application = App}) -> App. %% @doc Gets the module of `Location' -spec get_module(Location :: location()) -> module(). get_module(#?LOCATION{module = Module}) -> Module. %% @doc Gets the function of `Location' -spec get_function(Location :: location()) -> atom(). get_function(#?LOCATION{function = Function}) -> Function. %% @doc Gets the line of `Location' -spec get_line(Location :: location()) -> line(). get_line(#?LOCATION{line_or_anno = LineOrAnno}) when is_integer(LineOrAnno) -> LineOrAnno; get_line(#?LOCATION{line_or_anno = LineOrAnno}) -> erl_anno:line(LineOrAnno).