%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2006-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% %% %%% @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"). %%%-------------------------------------------------------------------- %%% 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), file: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 ~p could not be copied to ~p. "++ "Reason: ~p~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 ~p could not be copied to ~p. "++ "Reason: ~p~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: ~p, Args: ~p~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", "Progress Log
\n\n")), Fd. close_ct_master_log(Fd) -> io:put_chars(Fd,["",footer()]), file:close(Fd). config_table(Vars) -> [config_table_header()|config_table1(Vars)]. config_table_header() -> ["Configuration
\n", xhtml(["
Key | Value |
---|
Node | \n", "Log | \n", xhtml("", "|
---|---|---|
", timestamp(Dir)," | \n", "",MasterStr," | \n", "",NodesStr," | \n", "
History | \n" "Master Host | \n" "Test Nodes | \n", xhtml("", "
---|
\n", "
\n", "
\n"),
"Updated: ", current_time(), "",
xhtml("
\n", "
\n"),
xhtml("\n", "