1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2018. 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-module(mod_log).
22
23%% Application internal API
24-export([error_log/2, security_log/2, report_error/2]).
25
26%% Callback API
27-export([do/1, load/2, store/2, remove/1]).
28
29-include("httpd.hrl").
30-include("httpd_internal.hrl").
31-define(VMODULE,"LOG").
32
33%%%=========================================================================
34%%%  API
35%%%=========================================================================
36
37%% security log
38security_log(Info, ReasonStr) ->
39    Date = httpd_util:custom_date(),
40    case httpd_log:security_entry(security_log, no_security_log, Info,
41				 Date, ReasonStr) of
42	no_security_log ->
43	    ok;
44	{Log, Entry} ->
45	    io:format(Log, "~s", [Entry])
46    end.
47
48%% error_log
49error_log(Info, Reason) ->
50    Date = httpd_util:custom_date(),
51    error_log(Info, Date, Reason).
52
53error_log(Info, Date, Reason) ->
54    case httpd_log:error_entry(error_log, no_error_log,
55			       Info, Date, Reason) of
56	no_error_log ->
57	    ok;
58	{Log, Entry} ->
59	    io:format(Log, "~s", [Entry])
60    end.
61
62report_error(ConfigDB, Error) ->
63    Date = httpd_util:custom_date(),
64    case httpd_log:error_report_entry(error_log, no_error_log, ConfigDB,
65				      Date, Error) of
66	no_error_log ->
67	    ok;
68	{Log, Entry} ->
69	    io:format(Log, "~s", [Entry])
70    end.
71
72%%%=========================================================================
73%%%  CALLBACK API
74%%%=========================================================================
75%%--------------------------------------------------------------------------
76%% do(ModData) -> {proceed, OldData} | {proceed, NewData} | {break, NewData}
77%%                | done
78%%     ModData = #mod{}
79%%
80%% Description:  See httpd(3) ESWAPI CALLBACK FUNCTIONS
81%%-------------------------------------------------------------------------
82do(Info) ->
83    AuthUser = auth_user(Info#mod.data),
84    Date     = httpd_util:custom_date(),
85    log_internal_info(Info,Date,Info#mod.data),
86    case proplists:get_value(status, Info#mod.data) of
87	%% A status code has been generated!
88	{StatusCode, _PhraseArgs, Reason} ->
89	    transfer_log(Info,"-",AuthUser,Date,StatusCode,0),
90	    if
91		StatusCode >= 400 ->
92		    error_log(Info,Date,Reason);
93		true ->
94		    not_an_error
95	    end,
96	    {proceed,Info#mod.data};
97	%% No status code has been generated!
98	undefined ->
99	    case proplists:get_value(response, Info#mod.data) of
100		{already_sent,StatusCode,Size} ->
101		    transfer_log(Info,"-",AuthUser,Date,StatusCode,Size),
102		    {proceed,Info#mod.data};
103		{response, Head, _Body} ->
104                    Size = content_length(Head),
105		    Code = proplists:get_value(code,Head,unknown),
106		    transfer_log(Info, "-", AuthUser, Date, Code, Size),
107		    {proceed, Info#mod.data};
108		{StatusCode, Response} ->
109		    transfer_log(Info, "-", AuthUser, Date, StatusCode,
110				 httpd_util:flatlength(Response)),
111		    {proceed,Info#mod.data};
112		undefined ->
113		    transfer_log(Info,"-",AuthUser,Date,200,0),
114		    {proceed,Info#mod.data}
115	    end
116    end.
117
118%%--------------------------------------------------------------------------
119%% load(Line, Context) ->  eof | ok | {ok, NewContext} |
120%%                     {ok, NewContext, Directive} |
121%%                     {ok, NewContext, DirectiveList} | {error, Reason}
122%% Line = string()
123%% Context = NewContext = DirectiveList = [Directive]
124%% Directive = {DirectiveKey , DirectiveValue}
125%% DirectiveKey = DirectiveValue = term()
126%% Reason = term()
127%%
128%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
129%%-------------------------------------------------------------------------
130load("TransferLog " ++ TransferLog, []) ->
131    {ok,[],{transfer_log,string:strip(TransferLog)}};
132load("ErrorLog " ++ ErrorLog, []) ->
133    {ok,[],{error_log,string:strip(ErrorLog)}};
134load("SecurityLog " ++ SecurityLog, []) ->
135    {ok, [], {security_log, string:strip(SecurityLog)}}.
136
137%%--------------------------------------------------------------------------
138%% store(Directive, DirectiveList) -> {ok, NewDirective} |
139%%                                    {ok, [NewDirective]} |
140%%                                    {error, Reason}
141%% Directive = {DirectiveKey , DirectiveValue}
142%% DirectiveKey = DirectiveValue = term()
143%% Reason = term()
144%%
145%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
146%%-------------------------------------------------------------------------
147store({transfer_log,TransferLog}, ConfigList) when is_list(TransferLog)->
148    case create_log(TransferLog,ConfigList) of
149	{ok,TransferLogStream} ->
150	    {ok,{transfer_log,TransferLogStream}};
151	{error,Reason} ->
152	    {error,Reason}
153    end;
154store({transfer_log,TransferLog}, _) ->
155    {error, {wrong_type, {transfer_log, TransferLog}}};
156store({error_log,ErrorLog}, ConfigList) when is_list(ErrorLog) ->
157    case create_log(ErrorLog,ConfigList) of
158	{ok,ErrorLogStream} ->
159	    {ok,{error_log,ErrorLogStream}};
160	{error,Reason} ->
161	    {error,Reason}
162    end;
163store({error_log,ErrorLog}, _) ->
164    {error, {wrong_type, {error_log, ErrorLog}}};
165store({security_log, SecurityLog}, ConfigList) when is_list(SecurityLog) ->
166    case create_log(SecurityLog, ConfigList) of
167	{ok, SecurityLogStream} ->
168	    {ok, {security_log, SecurityLogStream}};
169	{error, Reason} ->
170	    {error, Reason}
171    end;
172store({security_log, SecurityLog}, _) ->
173    {error, {wrong_type, {security_log, SecurityLog}}}.
174
175%%--------------------------------------------------------------------------
176%% remove(ConfigDb) -> _
177%%
178%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
179%%-------------------------------------------------------------------------
180remove(ConfigDB) ->
181    lists:foreach(fun([Stream]) -> file:close(Stream) end,
182		  ets:match(ConfigDB,{transfer_log,'$1'})),
183    lists:foreach(fun([Stream]) -> file:close(Stream) end,
184		  ets:match(ConfigDB,{error_log,'$1'})),
185    lists:foreach(fun([Stream]) -> file:close(Stream) end,
186		  ets:match(ConfigDB,{security_log,'$1'})),
187    ok.
188
189%%%========================================================================
190%%% Internal functions
191%%%========================================================================
192%% transfer_log
193transfer_log(Info,RFC931,AuthUser,Date,StatusCode,Bytes) ->
194    case httpd_log:access_entry(transfer_log, no_transfer_log,
195				Info, RFC931, AuthUser, Date,
196				StatusCode, Bytes) of
197	no_transfer_log ->
198	    ok;
199	{Log, Entry} ->
200	    io:format(Log, "~s", [Entry])
201    end.
202
203create_log(LogFile, ConfigList) ->
204    Filename = string:strip(LogFile),
205    case filename:pathtype(Filename) of
206	absolute ->
207	    case file:open(Filename, [read, write]) of
208		{ok,LogStream} ->
209		    file:position(LogStream,{eof,0}),
210		    {ok,LogStream};
211		{error,_} ->
212		    {error,?NICE("Can't create "++Filename)}
213	    end;
214	volumerelative ->
215	    case file:open(Filename, [read, write]) of
216		{ok,LogStream} ->
217		    file:position(LogStream,{eof,0}),
218		    {ok,LogStream};
219		{error,_} ->
220		    {error,?NICE("Can't create "++Filename)}
221	    end;
222	relative ->
223	    case proplists:get_value(server_root,ConfigList) of
224		undefined ->
225		    {error,
226		     ?NICE(Filename++
227			   " is an invalid logfile name beacuse "
228			   "ServerRoot is not defined")};
229		ServerRoot ->
230		    AbsoluteFilename=filename:join(ServerRoot,Filename),
231		    case file:open(AbsoluteFilename, [read, write]) of
232			{ok,LogStream} ->
233			    file:position(LogStream,{eof,0}),
234			    {ok,LogStream};
235			{error, _Reason} ->
236			    {error,?NICE("Can't create "++AbsoluteFilename)}
237		    end
238	    end
239    end.
240
241%% log_internal_info
242log_internal_info(_Info, _Date, []) ->
243    ok;
244log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) ->
245    error_log(Info, Date, Reason),
246    log_internal_info(Info,Date,Rest);
247log_internal_info(Info,Date,[_|Rest]) ->
248    log_internal_info(Info,Date,Rest).
249
250auth_user(Data) ->
251    case proplists:get_value(remote_user, Data) of
252	undefined ->
253	    "-";
254	RemoteUser ->
255	    RemoteUser
256    end.
257
258content_length(Head) ->
259    case proplists:get_value(content_length, Head) of
260        undefined ->
261            unknown;
262        Size ->
263            list_to_integer(Size)
264    end.
265