1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2012-2017. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%%----------------------------------------------------------------------
21%% CT hook for logging of connections.
22%%
23%% HookOptions can be hardcoded in the test suite:
24%%
25%% suite() ->
26%%    [{ct_hooks, [{cth_conn_log,
27%%                  [{conn_mod(),hook_options()}]}]}].
28%%
29%% or specified in a configuration file:
30%%
31%% {ct_conn_log,[{conn_mod(),hook_options()}]}.
32%%
33%% The conn_mod() is the common test module implementing the protocol,
34%% e.g. ct_netconfc, ct_telnet, etc. This module must log by calling
35%%
36%% error_logger:info_report(ConnLogInfo,Data).
37%% ConnLogInfo = #conn_log{} | {ct_connection,Action,ConnName}
38%% Action = open | close | send | recv | term()
39%% ConnName = atom() - The 'KeyOrName' argument used when opening the connection
40%%
41%% ct_conn_log_h will print to html log or separate file (depending on
42%% log_type() option). conn_mod() must implement and export
43%%
44%% format_data(log_type(), Data).
45%%
46%% If logging to separate file, ct_conn_log_h will also log error
47%% reports which are witten like this:
48%%
49%% error_logger:error_report([{ct_connection,ConnName} | Report]).
50%%
51%%----------------------------------------------------------------------
52-module(cth_conn_log).
53
54-include_lib("common_test/include/ct.hrl").
55
56-export([init/2,
57	 pre_init_per_testcase/4,
58	 post_end_per_testcase/5]).
59
60%%----------------------------------------------------------------------
61%% Type declarations
62%%----------------------------------------------------------------------
63-type hook_options() :: ct:conn_log_options().
64-type log_type() :: ct:conn_log_type().
65-type conn_mod() :: ct:conn_log_mod().
66%%----------------------------------------------------------------------
67
68-spec init(Id, HookOpts) -> Result when
69      Id :: term(),
70      HookOpts :: hook_options(),
71      Result :: {ok,[{conn_mod(),{log_type(),[ct:key_or_name()]}}]}.
72init(_Id, HookOpts) ->
73    ConfOpts = ct:get_config(ct_conn_log,[]),
74    {ok,merge_log_info(ConfOpts,HookOpts)}.
75
76merge_log_info([{Mod,ConfOpts}|ConfList],HookList) ->
77    {Opts,HookList1} =
78	case lists:keytake(Mod,1,HookList) of
79	    false ->
80		{ConfOpts,HookList};
81	    {value,{_,HookOpts},HL1} ->
82		{ConfOpts ++ HookOpts, HL1} % ConfOpts overwrites HookOpts!
83	end,
84    [{Mod,get_log_opts(Mod,Opts)} | merge_log_info(ConfList,HookList1)];
85merge_log_info([],HookList) ->
86    [{Mod,get_log_opts(Mod,Opts)} || {Mod,Opts} <- HookList].
87
88get_log_opts(Mod,Opts) ->
89    DefaultLogType = if Mod == ct_telnet -> raw;
90			true -> html
91		     end,
92    LogType = proplists:get_value(log_type,Opts,DefaultLogType),
93    Hosts = proplists:get_value(hosts,Opts,[]),
94    {LogType,Hosts}.
95
96pre_init_per_testcase(_Suite,TestCase,Config,CthState) ->
97    Logs =
98	lists:map(
99	  fun({ConnMod,{LogType,Hosts}}) ->
100		  ct_util:set_testdata({{?MODULE,ConnMod},LogType}),
101		  case LogType of
102		      LogType when LogType==raw; LogType==pretty ->
103			  Dir = ?config(priv_dir,Config),
104			  TCStr = atom_to_list(TestCase),
105			  ConnModStr = atom_to_list(ConnMod),
106			  DefLogName = TCStr  ++ "-" ++ ConnModStr ++ ".txt",
107			  DefLog = filename:join(Dir,DefLogName),
108			  Ls = [{Host,
109				 filename:join(Dir,TCStr ++ "-"++
110						   atom_to_list(Host) ++ "-" ++
111						   ConnModStr ++
112						   ".txt")}
113				|| Host <- Hosts]
114			      ++[{default,DefLog}],
115			  Str =
116			      "<table borders=1>"
117			      "<b>" ++ ConnModStr ++ " logs:</b>\n" ++
118			      [io_lib:format(
119				 "<tr><td>~tp</td><td><a href=\"~ts\">~ts</a>"
120				 "</td></tr>",
121				 [S,ct_logs:uri(L),filename:basename(L)])
122			       || {S,L} <- Ls] ++
123			      "</table>",
124			  ct:log(Str,[],[no_css]),
125			  {ConnMod,{LogType,Ls}};
126		      _ ->
127			  {ConnMod,{LogType,[]}}
128		  end
129	  end,
130	  CthState),
131
132    GL = group_leader(),
133    Update =
134	fun(Init) when Init == undefined; Init == [] ->
135		error_logger:add_report_handler(ct_conn_log_h,{GL,Logs}),
136		[TestCase];
137	   (PrevUsers) ->
138		error_logger:info_report(update,{GL,Logs}),
139		receive
140		    {updated,GL} ->
141			[TestCase|PrevUsers]
142		after
143		    5000 ->
144			{error,no_response}
145		end
146	end,
147    ct_util:update_testdata(?MODULE, Update, [create]),
148    {Config,CthState}.
149
150post_end_per_testcase(_Suite,TestCase,_Config,Return,CthState) ->
151    Update =
152	fun(PrevUsers) ->
153		case lists:delete(TestCase, PrevUsers) of
154		    [] ->
155			'$delete';
156		    PrevUsers1 ->
157			PrevUsers1
158		end
159	end,
160    case ct_util:update_testdata(?MODULE, Update) of
161	deleted ->
162	    _ = [ct_util:delete_testdata({?MODULE,ConnMod}) ||
163		{ConnMod,_} <- CthState],
164	    error_logger:delete_report_handler(ct_conn_log_h);
165	{error,no_response} ->
166	    exit({?MODULE,no_response_from_logger});
167	_PrevUsers ->
168	    ok
169    end,
170    {Return,CthState}.
171
172