%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2012-2013. 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%
%%
%%----------------------------------------------------------------------
%% CT hook for logging of connections.
%%
%% HookOptions can be hardcoded in the test suite:
%%
%% suite() ->
%% [{ct_hooks, [{cth_conn_log,
%% [{ct_netconfc:conn_mod(),ct_netconfc:hook_options()}]}]}].
%%
%% or specified in a configuration file:
%%
%% {ct_conn_log,[{ct_netconfc:conn_mod(),ct_netconfc:hook_options()}]}.
%%
%% The conn_mod() is the common test module implementing the protocol,
%% e.g. ct_netconfc, ct_telnet, etc. This module must log by calling
%%
%% error_logger:info_report(ConnLogInfo,Data).
%% ConnLogInfo = #conn_log{} | {ct_connection,Action,ConnName}
%% Action = open | close | send | recv | term()
%% ConnName = atom() - The 'KeyOrName' argument used when opening the connection
%%
%% ct_conn_log_h will print to html log or separate file (depending on
%% log_type() option). conn_mod() must implement and export
%%
%% format_data(log_type(), Data).
%%
%% If logging to separate file, ct_conn_log_h will also log error
%% reports which are witten like this:
%%
%% error_logger:error_report([{ct_connection,ConnName} | Report]).
%%
%%----------------------------------------------------------------------
-module(cth_conn_log).
-include_lib("common_test/include/ct.hrl").
-export([init/2,
pre_init_per_testcase/3,
post_end_per_testcase/4]).
%%----------------------------------------------------------------------
%% Exported types
%%----------------------------------------------------------------------
-export_type([hook_options/0,
log_type/0,
conn_mod/0]).
%%----------------------------------------------------------------------
%% Type declarations
%%----------------------------------------------------------------------
-type hook_options() :: [hook_option()].
%% Options that can be given to `cth_conn_log' in the `ct_hook' statement.
-type hook_option() :: {log_type,log_type()} |
{hosts,[ct_gen_conn:key_or_name()]}.
-type log_type() :: raw | pretty | html | silent.
-type conn_mod() :: ct_netconfc | ct_telnet.
%%----------------------------------------------------------------------
-spec init(Id, HookOpts) -> Result when
Id :: term(),
HookOpts :: hook_options(),
Result :: {ok,[{conn_mod(),
{log_type(),[ct_gen_conn:key_or_name()]}}]}.
init(_Id, HookOpts) ->
ConfOpts = ct:get_config(ct_conn_log,[]),
{ok,merge_log_info(ConfOpts,HookOpts)}.
merge_log_info([{Mod,ConfOpts}|ConfList],HookList) ->
{Opts,HookList1} =
case lists:keytake(Mod,1,HookList) of
false ->
{ConfOpts,HookList};
{value,{_,HookOpts},HL1} ->
{ConfOpts ++ HookOpts, HL1} % ConfOpts overwrites HookOpts!
end,
[{Mod,get_log_opts(Opts)} | merge_log_info(ConfList,HookList1)];
merge_log_info([],HookList) ->
[{Mod,get_log_opts(Opts)} || {Mod,Opts} <- HookList].
get_log_opts(Opts) ->
LogType = proplists:get_value(log_type,Opts,html),
Hosts = proplists:get_value(hosts,Opts,[]),
{LogType,Hosts}.
pre_init_per_testcase(TestCase,Config,CthState) ->
Logs =
lists:map(
fun({ConnMod,{LogType,Hosts}}) ->
ct_util:set_testdata({{?MODULE,ConnMod},LogType}),
case LogType of
LogType when LogType==raw; LogType==pretty ->
Dir = ?config(priv_dir,Config),
TCStr = atom_to_list(TestCase),
ConnModStr = atom_to_list(ConnMod),
DefLogName = TCStr ++ "-" ++ ConnModStr ++ ".txt",
DefLog = filename:join(Dir,DefLogName),
Ls = [{Host,
filename:join(Dir,TCStr ++ "-"++
atom_to_list(Host) ++ "-" ++
ConnModStr ++
".txt")}
|| Host <- Hosts]
++[{default,DefLog}],
Str =
"<table borders=1>"
"<b>" ++ ConnModStr ++ " logs:</b>\n" ++
[io_lib:format(
"<tr><td>~p</td><td><a href=\"~ts\">~ts</a>"
"</td></tr>",
[S,ct_logs:uri(L),filename:basename(L)])
|| {S,L} <- Ls] ++
"</table>",
io:format(Str,[]),
{ConnMod,{LogType,Ls}};
_ ->
{ConnMod,{LogType,[]}}
end
end,
CthState),
GL = group_leader(),
Update =
fun(Init) when Init == undefined; Init == [] ->
%%! --- Tue Jan 28 12:13:08 2014 --- peppe was here!
io:format(user, "### ~p: ADDING NEW HANDLER FOR ~p~n",
[TestCase,GL]),
error_logger:add_report_handler(ct_conn_log_h,{GL,Logs}),
[TestCase];
(PrevUsers) ->
%%! --- Tue Jan 28 12:13:08 2014 --- peppe was here!
io:format(user, "### ~p: CONNECTING ~p TO EXISTING HANDLER~n",
[TestCase,GL]),
error_logger:info_report(update,{GL,Logs}),
receive
{updated,GL} ->
[TestCase|PrevUsers]
after
5000 ->
{error,no_response}
end
end,
ct_util:update_testdata(?MODULE, Update, [create]),
{Config,CthState}.
post_end_per_testcase(TestCase,_Config,Return,CthState) ->
Update =
fun(PrevUsers) ->
case lists:delete(TestCase, PrevUsers) of
[] ->
'$delete';
PrevUsers1 ->
PrevUsers1
end
end,
case ct_util:update_testdata(?MODULE, Update) of
deleted ->
[ct_util:delete_testdata({?MODULE,ConnMod}) ||
{ConnMod,_} <- CthState],
%%! --- Tue Jan 28 13:29:37 2014 --- peppe was here!
io:format(user, "### ~p: REMOVING ERROR LOGGER~n", [TestCase]),
error_logger:delete_report_handler(ct_conn_log_h);
{error,no_response} ->
exit({?MODULE,no_response_from_logger});
_PrevUsers ->
%%! --- Tue Jan 28 13:29:37 2014 --- peppe was here!
io:format(user, "### ~p: *NOT* REMOVING ERROR LOGGER~n", [TestCase]),
ok
end,
{Return,CthState}.