%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: The main interface module of the inets application
%%----------------------------------------------------------------------
-module(inets_trace).
%% API
-export([enable/2, enable/3,
disable/0,
set_level/1, set_level/2,
report_event/4]).
%%====================================================================
%% API
%%====================================================================
%%-----------------------------------------------------------------
%% enable(Level, Destination) -> void()
%% enable(Level, Destination, Service) -> void()
%%
%% Parameters:
%% Level -> max | min | integer()
%% Destination -> File | Port | io | HandlerSpec
%% Service -> httpc | httpd | ftpc | tftp | all
%% File -> string()
%% Port -> integer()
%% Verbosity -> true | false
%% HandlerSpec = {function(), Data}
%% Data = term()
%%
%% Description:
%% This function is used to start tracing at level Level and send
%% the result either to the file File, the port Port or to a
%% trace handler.
%% Note that it starts a tracer server.
%% When Destination is the atom io (or the tuple {io, Verbosity}),
%% all (printable) inets trace events (trace_ts events which has
%% Severity withing Limit) will be written to stdout using io:format.
%%
%%-----------------------------------------------------------------
enable(Level, Dest) ->
enable(Level, Dest, all).
enable(Level, Dest, Service) ->
case valid_trace_service(Service) of
true ->
enable2(Level, Dest, Service);
false ->
{error, {invalid_service, Service}}
end.
enable2(Level, File, Service)
when is_list(File) ->
case file:open(File, [write]) of
{ok, Fd} ->
HandleSpec = {fun handle_trace/2, {Service, Fd}},
do_enable(Level, process, HandleSpec);
Err ->
Err
end;
enable2(Level, Port, _) when is_integer(Port) ->
do_enable(Level, port, dbg:trace_port(ip, Port));
enable2(Level, io, Service) ->
HandleSpec = {fun handle_trace/2, {Service, standard_io}},
do_enable(Level, process, HandleSpec);
enable2(Level, {Fun, _Data} = HandleSpec, _) when is_function(Fun) ->
do_enable(Level, process, HandleSpec).
do_enable(Level, Type, HandleSpec) ->
case dbg:tracer(Type, HandleSpec) of
{ok, _} ->
set_level(Level),
ok;
Error ->
Error
end.
valid_trace_service(all) ->
true;
valid_trace_service(Service) ->
lists:member(Service, [httpc, httpd, ftpc, tftp]).
%%-----------------------------------------------------------------
%% disable() -> void()
%%
%% Description:
%% This function is used to stop tracing.
%%-----------------------------------------------------------------
disable() ->
%% This is to make handle_trace/2 close the output file (if the
%% event gets there before dbg closes)
inets_trace:report_event(100, "stop trace", stop_trace, [stop_trace]),
dbg:stop().
%%-----------------------------------------------------------------
%% set_level(Level) -> void()
%%
%% Parameters:
%% Level -> max | min | integer()
%%
%% Description:
%% This function is used to change the trace level when tracing has
%% already been started.
%%-----------------------------------------------------------------
set_level(Level) ->
set_level(Level, all).
set_level(Level, Service) ->
Pat = make_pattern(?MODULE, Service, Level),
change_pattern(Pat).
make_pattern(Mod, Service, Level)
when is_atom(Mod) andalso is_atom(Service) ->
case Level of
min ->
{Mod, Service, []};
max ->
Head = ['$1', '_', '_', '_'],
Body = [],
Cond = [],
{Mod, Service, [{Head, Cond, Body}]};
DetailLevel when is_integer(DetailLevel) ->
Head = ['$1', '_', '_', '_'],
Body = [],
Cond = [{ '=<', '$1', DetailLevel}],
{Mod, Service, [{Head, Cond, Body}]};
_ ->
exit({bad_level, Level})
end.
change_pattern({Mod, Service, Pattern})
when is_atom(Mod) andalso is_atom(Service) ->
MFA = {Mod, report_event, 4},
case Pattern of
[] ->
try
error_to_exit(ctp, dbg:ctp(MFA)),
error_to_exit(p, dbg:p(all, clear))
catch
exit:{Where, Reason} ->
{error, {Where, Reason}}
end;
List when is_list(List) ->
try
error_to_exit(ctp, dbg:ctp(MFA)),
error_to_exit(tp, dbg:tp(MFA, Pattern)),
error_to_exit(p, dbg:p(all, [call, timestamp]))
catch
exit:{Where, Reason} ->
{error, {Where, Reason}}
end
end.
error_to_exit(_Where, {ok, _} = OK) ->
OK;
error_to_exit(Where, {error, Reason}) ->
exit({Where, Reason}).
%%-----------------------------------------------------------------
%% report_event(Severity, Label, Service, Content)
%%
%% Parameters:
%% Severity -> 0 =< integer() =< 100
%% Label -> string()
%% Service -> httpd | httpc | ftp | tftp
%% Content -> [{tag, term()}]
%%
%% Description:
%% This function is used to generate trace events, that is,
%% put trace on this function.
%%-----------------------------------------------------------------
report_event(Severity, Label, Service, Content)
when (is_integer(Severity) andalso
(Severity >= 0) andalso (100 >= Severity)) andalso
is_list(Label) andalso
is_atom(Service) andalso
is_list(Content) ->
hopefully_traced.
%% ----------------------------------------------------------------------
%% handle_trace(Event, Fd) -> Verbosity
%%
%% Parameters:
%% Event -> The trace event
%% Fd -> standard_io | file_descriptor() | trace_port()
%%
%% Description:
%% This function is used to "receive" and pretty print the trace events.
%% Events are printed if:
%% - Verbosity is max
%% - Severity is =< Verbosity (e.g. Severity = 30, and Verbosity = 40)
%% Events are not printed if:
%% - Verbosity is min
%% - Severity is > Verbosity
%%-----------------------------------------------------------------
handle_trace(_, closed_file = Fd) ->
Fd;
handle_trace({trace_ts, _Who, call,
{?MODULE, report_event,
[_Sev, "stop trace", stop_trace, [stop_trace]]},
Timestamp},
{_, standard_io} = Fd) ->
(catch io:format(standard_io, "stop trace at ~s~n", [format_timestamp(Timestamp)])),
Fd;
handle_trace({trace_ts, _Who, call,
{?MODULE, report_event,
[_Sev, "stop trace", stop_trace, [stop_trace]]},
Timestamp},
standard_io = Fd) ->
(catch io:format(Fd, "stop trace at ~s~n", [format_timestamp(Timestamp)])),
Fd;
handle_trace({trace_ts, _Who, call,
{?MODULE, report_event,
[_Sev, "stop trace", stop_trace, [stop_trace]]},
Timestamp},
{_Service, Fd}) ->
(catch io:format(Fd, "stop trace at ~s~n", [format_timestamp(Timestamp)])),
(catch file:close(Fd)),
closed_file;
handle_trace({trace_ts, _Who, call,
{?MODULE, report_event,
[_Sev, "stop trace", stop_trace, [stop_trace]]},
Timestamp},
Fd) ->
(catch io:format(Fd, "stop trace at ~s~n", [format_timestamp(Timestamp)])),
(catch file:close(Fd)),
closed_file;
handle_trace({trace_ts, Who, call,
{?MODULE, report_event,
[Sev, Label, Service, Content]}, Timestamp},
Fd) ->
(catch print_inets_trace(Fd, Sev, Timestamp, Who,
Label, Service, Content)),
Fd;
handle_trace(Event, Fd) ->
(catch print_trace(Fd, Event)),
Fd.
print_inets_trace({Service, Fd},
Sev, Timestamp, Who, Label, Service, Content) ->
do_print_inets_trace(Fd, Sev, Timestamp, Who, Label, Service, Content);
print_inets_trace({ServiceA, Fd},
Sev, Timestamp, Who, Label, ServiceB, Content)
when (ServiceA =:= all) ->
do_print_inets_trace(Fd, Sev, Timestamp, Who, Label, ServiceB, Content);
print_inets_trace({ServiceA, _Fd},
_Sev, _Timestamp, _Who, _Label, ServiceB, _Content)
when ServiceA =/= ServiceB ->
ok;
print_inets_trace(Fd, Sev, Timestamp, Who, Label, Service, Content) ->
do_print_inets_trace(Fd, Sev, Timestamp, Who, Label, Service, Content).
do_print_inets_trace(Fd, Sev, Timestamp, Who, Label, Service, Content) ->
Ts = format_timestamp(Timestamp),
io:format(Fd, "[inets ~w trace ~w ~w ~s] ~s "
"~n Content: ~p"
"~n",
[Service, Sev, Who, Ts, Label, Content]).
print_trace({_, Fd}, Event) ->
do_print_trace(Fd, Event);
print_trace(Fd, Event) ->
do_print_trace(Fd, Event).
do_print_trace(Fd, {trace, Who, What, Where}) ->
io:format(Fd, "[trace]"
"~n Who: ~p"
"~n What: ~p"
"~n Where: ~p"
"~n", [Who, What, Where]);
do_print_trace(Fd, {trace, Who, What, Where, Extra}) ->
io:format(Fd, "[trace]"
"~n Who: ~p"
"~n What: ~p"
"~n Where: ~p"
"~n Extra: ~p"
"~n", [Who, What, Where, Extra]);
do_print_trace(Fd, {trace_ts, Who, What, Where, When}) ->
Ts = format_timestamp(When),
io:format(Fd, "[trace ~s]"
"~n Who: ~p"
"~n What: ~p"
"~n Where: ~p"
"~n", [Ts, Who, What, Where]);
do_print_trace(Fd, {trace_ts, Who, What, Where, Extra, When}) ->
Ts = format_timestamp(When),
io:format(Fd, "[trace ~s]"
"~n Who: ~p"
"~n What: ~p"
"~n Where: ~p"
"~n Extra: ~p"
"~n", [Ts, Who, What, Where, Extra]);
do_print_trace(Fd, {seq_trace, What, Where}) ->
io:format(Fd, "[seq trace]"
"~n What: ~p"
"~n Where: ~p"
"~n", [What, Where]);
do_print_trace(Fd, {seq_trace, What, Where, When}) ->
Ts = format_timestamp(When),
io:format(Fd, "[seq trace ~s]"
"~n What: ~p"
"~n Where: ~p"
"~n", [Ts, What, Where]);
do_print_trace(Fd, {drop, Num}) ->
io:format(Fd, "[drop trace] ~p~n", [Num]);
do_print_trace(Fd, Trace) ->
io:format(Fd, "[trace] "
"~n ~p"
"~n", [Trace]).
format_timestamp({_N1, _N2, N3} = Now) ->
{Date, Time} = calendar:now_to_datetime(Now),
{YYYY,MM,DD} = Date,
{Hour,Min,Sec} = Time,
FormatDate =
io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
[YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
lists:flatten(FormatDate).