%% ``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 via the world wide web 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.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
%% $Id: mod_log.erl,v 1.1 2008/12/17 09:53:35 mikpe Exp $
%%
-module(mod_log).
-export([do/1,error_log/5,security_log/2,load/2,store/2,remove/1]).
-export([report_error/2]).
-include("httpd.hrl").
-define(VMODULE,"LOG").
-include("httpd_verbosity.hrl").
%% do
do(Info) ->
AuthUser = auth_user(Info#mod.data),
Date = custom_date(),
log_internal_info(Info,Date,Info#mod.data),
case httpd_util:key1search(Info#mod.data,status) of
%% A status code has been generated!
{StatusCode,PhraseArgs,Reason} ->
transfer_log(Info,"-",AuthUser,Date,StatusCode,0),
if
StatusCode >= 400 ->
error_log(Info,Date,Reason);
true ->
not_an_error
end,
{proceed,Info#mod.data};
%% No status code has been generated!
undefined ->
case httpd_util:key1search(Info#mod.data,response) of
{already_sent,StatusCode,Size} ->
transfer_log(Info,"-",AuthUser,Date,StatusCode,Size),
{proceed,Info#mod.data};
{response,Head,Body} ->
Size=httpd_util:key1search(Head,content_length,unknown),
Code=httpd_util:key1search(Head,code,unknown),
transfer_log(Info,"-",AuthUser,Date,Code,Size),
{proceed,Info#mod.data};
{StatusCode,Response} ->
transfer_log(Info,"-",AuthUser,Date,200,
httpd_util:flatlength(Response)),
{proceed,Info#mod.data};
undefined ->
transfer_log(Info,"-",AuthUser,Date,200,0),
{proceed,Info#mod.data}
end
end.
custom_date() ->
LocalTime=calendar:local_time(),
UniversalTime=calendar:universal_time(),
Minutes=round(diff_in_minutes(LocalTime,UniversalTime)),
{{YYYY,MM,DD},{Hour,Min,Sec}}=LocalTime,
Date =
io_lib:format("~.2.0w/~.3s/~.4w:~.2.0w:~.2.0w:~.2.0w ~c~.2.0w~.2.0w",
[DD, httpd_util:month(MM), YYYY, Hour, Min, Sec,
sign(Minutes),
abs(Minutes) div 60, abs(Minutes) rem 60]),
lists:flatten(Date).
diff_in_minutes(L,U) ->
(calendar:datetime_to_gregorian_seconds(L) -
calendar:datetime_to_gregorian_seconds(U))/60.
sign(Minutes) when Minutes > 0 ->
$+;
sign(Minutes) ->
$-.
auth_user(Data) ->
case httpd_util:key1search(Data,remote_user) of
undefined ->
"-";
RemoteUser ->
RemoteUser
end.
%% log_internal_info
log_internal_info(Info,Date,[]) ->
ok;
log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) ->
error_log(Info,Date,Reason),
log_internal_info(Info,Date,Rest);
log_internal_info(Info,Date,[_|Rest]) ->
log_internal_info(Info,Date,Rest).
%% transfer_log
transfer_log(Info,RFC931,AuthUser,Date,StatusCode,Bytes) ->
case httpd_util:lookup(Info#mod.config_db,transfer_log) of
undefined ->
no_transfer_log;
TransferLog ->
{PortNumber,RemoteHost}=(Info#mod.init_data)#init_data.peername,
case (catch io:format(TransferLog, "~s ~s ~s [~s] \"~s\" ~w ~w~n",
[RemoteHost, RFC931, AuthUser,
Date, Info#mod.request_line,
StatusCode, Bytes])) of
ok ->
ok;
Error ->
error_logger:error_report(Error)
end
end.
%% security log
security_log(Info, Reason) ->
case httpd_util:lookup(Info#mod.config_db, security_log) of
undefined ->
no_security_log;
SecurityLog ->
io:format(SecurityLog,"[~s] ~s~n", [custom_date(), Reason])
end.
%% error_log
error_log(Info,Date,Reason) ->
case httpd_util:lookup(Info#mod.config_db, error_log) of
undefined ->
no_error_log;
ErrorLog ->
{PortNumber,RemoteHost}=(Info#mod.init_data)#init_data.peername,
io:format(ErrorLog,"[~s] access to ~s failed for ~s, reason: ~p~n",
[Date,Info#mod.request_uri,RemoteHost,Reason])
end.
error_log(SocketType,Socket,ConfigDB,{PortNumber,RemoteHost},Reason) ->
case httpd_util:lookup(ConfigDB,error_log) of
undefined ->
no_error_log;
ErrorLog ->
Date=custom_date(),
io:format(ErrorLog,"[~s] server crash for ~s, reason: ~p~n",
[Date,RemoteHost,Reason]),
ok
end.
report_error(ConfigDB,Error) ->
case httpd_util:lookup(ConfigDB,error_log) of
undefined ->
no_error_log;
ErrorLog ->
Date=custom_date(),
io:format(ErrorLog,"[~s] reporting error: ~s~n",[Date,Error]),
ok
end.
%%
%% Configuration
%%
%% load
load([$T,$r,$a,$n,$s,$f,$e,$r,$L,$o,$g,$ |TransferLog],[]) ->
{ok,[],{transfer_log,httpd_conf:clean(TransferLog)}};
load([$E,$r,$r,$o,$r,$L,$o,$g,$ |ErrorLog],[]) ->
{ok,[],{error_log,httpd_conf:clean(ErrorLog)}};
load([$S,$e,$c,$u,$r,$i,$t,$y,$L,$o,$g,$ |SecurityLog], []) ->
{ok, [], {security_log, httpd_conf:clean(SecurityLog)}}.
%% store
store({transfer_log,TransferLog},ConfigList) ->
case create_log(TransferLog,ConfigList) of
{ok,TransferLogStream} ->
{ok,{transfer_log,TransferLogStream}};
{error,Reason} ->
{error,Reason}
end;
store({error_log,ErrorLog},ConfigList) ->
case create_log(ErrorLog,ConfigList) of
{ok,ErrorLogStream} ->
{ok,{error_log,ErrorLogStream}};
{error,Reason} ->
{error,Reason}
end;
store({security_log, SecurityLog},ConfigList) ->
case create_log(SecurityLog, ConfigList) of
{ok, SecurityLogStream} ->
{ok, {security_log, SecurityLogStream}};
{error, Reason} ->
{error, Reason}
end.
create_log(LogFile,ConfigList) ->
Filename = httpd_conf:clean(LogFile),
case filename:pathtype(Filename) of
absolute ->
case file:open(Filename, [read,write]) of
{ok,LogStream} ->
file:position(LogStream,{eof,0}),
{ok,LogStream};
{error,_} ->
{error,?NICE("Can't create "++Filename)}
end;
volumerelative ->
case file:open(Filename, [read,write]) of
{ok,LogStream} ->
file:position(LogStream,{eof,0}),
{ok,LogStream};
{error,_} ->
{error,?NICE("Can't create "++Filename)}
end;
relative ->
case httpd_util:key1search(ConfigList,server_root) of
undefined ->
{error,
?NICE(Filename++
" is an invalid logfile name beacuse ServerRoot is not defined")};
ServerRoot ->
AbsoluteFilename=filename:join(ServerRoot,Filename),
case file:open(AbsoluteFilename, [read,write]) of
{ok,LogStream} ->
file:position(LogStream,{eof,0}),
{ok,LogStream};
{error,Reason} ->
{error,?NICE("Can't create "++AbsoluteFilename)}
end
end
end.
%% remove
remove(ConfigDB) ->
lists:foreach(fun([Stream]) -> file:close(Stream) end,
ets:match(ConfigDB,{transfer_log,'$1'})),
lists:foreach(fun([Stream]) -> file:close(Stream) end,
ets:match(ConfigDB,{error_log,'$1'})),
lists:foreach(fun([Stream]) -> file:close(Stream) end,
ets:match(ConfigDB,{security_log,'$1'})),
ok.