%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2006-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%% @doc Logging functionality for Common Test Master.
%%%
%%%
This module implements a logger for the master
%%% node.
-module(ct_master_logs).
-export([start/2, make_all_runs_index/0, log/3, nodedir/2,
stop/0]).
-include("ct_util.hrl").
-record(state, {log_fd, start_time, logdir, rundir,
nodedir_ix_fd, nodes, nodedirs=[]}).
-define(ct_master_log_name, "ct_master_log.html").
-define(all_runs_name, "master_runs.html").
-define(nodedir_index_name, "index.html").
-define(details_file_name,"details.info").
-define(table_color,"lightblue").
-define(now, os:timestamp()).
%%%--------------------------------------------------------------------
%%% API
%%%--------------------------------------------------------------------
start(LogDir,Nodes) ->
Self = self(),
Pid = spawn_link(fun() -> init(Self,LogDir,Nodes) end),
MRef = erlang:monitor(process,Pid),
receive
{started,Pid,Result} ->
erlang:demonitor(MRef, [flush]),
{Pid,Result};
{'DOWN',MRef,process,_,Reason} ->
exit({could_not_start_process,?MODULE,Reason})
end.
log(Heading,Format,Args) ->
cast({log,self(),[{int_header(),[log_timestamp(?now),Heading]},
{Format,Args},
{int_footer(),[]}]}),
ok.
make_all_runs_index() ->
call(make_all_runs_index).
nodedir(Node,RunDir) ->
call({nodedir,Node,RunDir}).
stop() ->
case whereis(?MODULE) of
Pid when is_pid(Pid) ->
MRef = erlang:monitor(process,Pid),
?MODULE ! stop,
receive
{'DOWN',MRef,process,_,_} ->
ok
end;
undefined ->
ok
end,
ok.
%%%--------------------------------------------------------------------
%%% Logger process
%%%--------------------------------------------------------------------
init(Parent,LogDir,Nodes) ->
register(?MODULE,self()),
Time = calendar:local_time(),
RunDir = make_dirname(Time),
RunDirAbs = filename:join(LogDir,RunDir),
ok = make_dir(RunDirAbs),
_ = write_details_file(RunDirAbs,{node(),Nodes}),
case basic_html() of
true ->
put(basic_html, true);
BasicHtml ->
put(basic_html, BasicHtml),
%% copy priv files to log dir (both top dir and test run
%% dir) so logs are independent of Common Test installation
CTPath = code:lib_dir(common_test),
PrivFiles = [?css_default,?jquery_script,?tablesorter_script],
PrivFilesSrc = [filename:join(filename:join(CTPath, "priv"), F) ||
F <- PrivFiles],
PrivFilesDestTop = [filename:join(LogDir, F) || F <- PrivFiles],
PrivFilesDestRun = [filename:join(RunDirAbs, F) || F <- PrivFiles],
case copy_priv_files(PrivFilesSrc, PrivFilesDestTop) of
{error,Src1,Dest1,Reason1} ->
io:format(user, "ERROR! "++
"Priv file ~tp could not be copied to ~tp. "++
"Reason: ~tp~n",
[Src1,Dest1,Reason1]),
exit({priv_file_error,Dest1});
ok ->
case copy_priv_files(PrivFilesSrc, PrivFilesDestRun) of
{error,Src2,Dest2,Reason2} ->
io:format(user, "ERROR! "++
"Priv file ~tp could not be copied to ~tp. "++
"Reason: ~tp~n",
[Src2,Dest2,Reason2]),
exit({priv_file_error,Dest2});
ok ->
ok
end
end
end,
_ = make_all_runs_index(LogDir),
CtLogFd = open_ct_master_log(RunDirAbs),
NodeStr =
lists:flatten(lists:map(fun(N) ->
atom_to_list(N) ++ " "
end,Nodes)),
io:format(CtLogFd,int_header(),[log_timestamp(?now),"Test Nodes\n"]),
io:format(CtLogFd,"~ts\n",[NodeStr]),
io:put_chars(CtLogFd,[int_footer(),"\n"]),
NodeDirIxFd = open_nodedir_index(RunDirAbs,Time),
Parent ! {started,self(),{Time,RunDirAbs}},
loop(#state{log_fd=CtLogFd,
start_time=Time,
logdir=LogDir,
rundir=RunDirAbs,
nodedir_ix_fd=NodeDirIxFd,
nodes=Nodes,
nodedirs=lists:map(fun(N) ->
{N,""}
end,Nodes)}).
copy_priv_files([SrcF | SrcFs], [DestF | DestFs]) ->
case file:copy(SrcF, DestF) of
{error,Reason} ->
{error,SrcF,DestF,Reason};
_ ->
copy_priv_files(SrcFs, DestFs)
end;
copy_priv_files([], []) ->
ok.
loop(State) ->
receive
{log,_From,List} ->
Fd = State#state.log_fd,
Fun =
fun({Str,Args}) ->
case catch io:format(Fd,Str++"\n",Args) of
{'EXIT',Reason} ->
io:format(Fd,
"Logging fails! Str: ~tp, Args: ~tp~n",
[Str,Args]),
exit({logging_failed,Reason}),
ok;
_ ->
ok
end
end,
lists:foreach(Fun,List),
loop(State);
{make_all_runs_index,From} ->
_ = make_all_runs_index(State#state.logdir),
return(From,State#state.logdir),
loop(State);
{{nodedir,Node,RunDir},From} ->
print_nodedir(Node,RunDir,State#state.nodedir_ix_fd),
return(From,ok),
loop(State);
stop ->
_ = make_all_runs_index(State#state.logdir),
io:format(State#state.log_fd,
int_header()++int_footer(),
[log_timestamp(?now),"Finished!"]),
_ = close_ct_master_log(State#state.log_fd),
_ = close_nodedir_index(State#state.nodedir_ix_fd),
ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Master Log functions %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
open_ct_master_log(Dir) ->
FullName = filename:join(Dir,?ct_master_log_name),
{ok,Fd} = file:open(FullName,[write,{encoding,utf8}]),
io:put_chars(Fd,header("Common Test Master Log", {[],[1,2],[]})),
%% maybe add config info here later
io:put_chars(Fd,config_table([])),
io:put_chars(Fd,
"\n"),
io:put_chars(Fd,
xhtml("
\n\n\n")]].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% All Run Index functions %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
make_all_runs_index(LogDir) ->
FullName = filename:join(LogDir,?all_runs_name),
Match = filename:join(LogDir,logdir_prefix()++"*.*"),
Dirs = filelib:wildcard(Match),
DirsSorted = (catch sort_all_runs(Dirs)),
Header = all_runs_header(),
Index = [runentry(Dir) || Dir <- DirsSorted],
Result = file:write_file(FullName,
unicode:characters_to_binary(
Header++Index++index_footer())),
Result.
sort_all_runs(Dirs) ->
%% sort on time string, always last and on the format:
%% "YYYY-MM-DD_HH.MM.SS"
KeyList =
lists:map(fun(Dir) ->
case lists:reverse(string:lexemes(Dir,[$.,$_])) of
[SS,MM,HH,Date|_] ->
{{Date,HH,MM,SS},Dir};
_Other ->
throw(Dirs)
end
end,Dirs),
lists:reverse(lists:map(fun({_,Dir}) ->
Dir
end,lists:keysort(1,KeyList))).
runentry(Dir) ->
{MasterStr,NodesStr} =
case read_details_file(Dir) of
{Master,Nodes} when is_list(Nodes) ->
[_,Host] = string:lexemes(atom_to_list(Master),"@"),
{Host,lists:concat(lists:join(", ",Nodes))};
_Error ->
{"unknown",""}
end,
Index = filename:join(Dir,?nodedir_index_name),
["