1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2021. 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_disk_log).
22
23%% Application internal API
24-export([error_log/2, report_error/2, security_log/2]).
25
26%% Callback API
27-export([do/1, store/2, remove/1]).
28
29-define(VMODULE,"DISK_LOG").
30
31-include("httpd.hrl").
32-include("httpd_internal.hrl").
33
34%%%=========================================================================
35%%%  API
36%%%=========================================================================
37
38%% security_log
39security_log(#mod{config_db = ConfigDb} = Info, Event) ->
40    Format = get_log_format(ConfigDb),
41    Date = httpd_util:custom_date(),
42    case httpd_log:security_entry(security_disk_log, no_security_log,
43				  Info, Date, Event) of
44	no_security_log ->
45	    ok;
46	{Log, Entry} ->
47	    write(Log, Entry, Format)
48    end.
49
50report_error(ConfigDB, Error) ->
51    Format = get_log_format(ConfigDB),
52    Date = httpd_util:custom_date(),
53    case httpd_log:error_report_entry(error_disk_log, no_error_log, ConfigDB,
54				      Date, Error) of
55	no_error_log ->
56	    ok;
57	{Log, Entry} ->
58	    write(Log, Entry, Format)
59    end.
60
61error_log(Info, Reason) ->
62    Date = httpd_util:custom_date(),
63    error_log(Info, Date, Reason).
64
65error_log(#mod{config_db = ConfigDB} = Info, Date, Reason) ->
66    Format = get_log_format(ConfigDB),
67     case httpd_log:error_entry(error_disk_log, no_error_log,
68			       Info, Date, Reason) of
69	no_error_log ->
70	    ok;
71	{Log, Entry} ->
72	     write(Log, Entry, Format)
73    end.
74
75%%%=========================================================================
76%%%  CALLBACK API
77%%%=========================================================================
78%%--------------------------------------------------------------------------
79%% do(ModData) -> {proceed, OldData} | {proceed, NewData} | {break, NewData}
80%%                | done
81%%     ModData = #mod{}
82%%
83%% Description:  See httpd(3) ESWAPI CALLBACK FUNCTIONS
84%%-------------------------------------------------------------------------
85do(Info) ->
86    AuthUser  = auth_user(Info#mod.data),
87    Date      = httpd_util:custom_date(),
88    log_internal_info(Info,Date,Info#mod.data),
89    LogFormat = get_log_format(Info#mod.config_db),
90    case proplists:get_value(status, Info#mod.data) of
91	%% A status code has been generated!
92	{StatusCode, _PhraseArgs, Reason} ->
93	    transfer_log(Info, "-", AuthUser, Date, StatusCode, 0,
94			 LogFormat),
95	    if
96		StatusCode >= 400 ->
97		    error_log(Info, Date, Reason);
98		true ->
99		    not_an_error
100	    end,
101	    {proceed,Info#mod.data};
102	%% No status code has been generated!
103	undefined ->
104	    case proplists:get_value(response, Info#mod.data) of
105		{already_sent,StatusCode,Size} ->
106		    transfer_log(Info, "-", AuthUser, Date, StatusCode,
107				 Size, LogFormat),
108		    {proceed,Info#mod.data};
109
110		{response, Head, _Body} ->
111		    Size = proplists:get_value(content_length, Head, 0),
112		    Code = proplists:get_value(code, Head, 200),
113		    transfer_log(Info, "-", AuthUser, Date, Code,
114				 Size, LogFormat),
115		    {proceed,Info#mod.data};
116
117		{_StatusCode, Response} ->
118		    transfer_log(Info, "-", AuthUser, Date, 200,
119				 erlang:iolist_size(Response), LogFormat),
120		    {proceed,Info#mod.data};
121		undefined ->
122		    transfer_log(Info, "-", AuthUser, Date, 200,
123				 0, LogFormat),
124		    {proceed,Info#mod.data}
125	    end
126    end.
127
128%%--------------------------------------------------------------------------
129%% store(Directive, DirectiveList) -> {ok, NewDirective} |
130%%                                    {ok, [NewDirective]} |
131%%                                    {error, Reason}
132%% Directive = {DirectiveKey , DirectiveValue}
133%% DirectiveKey = DirectiveValue = term()
134%% Reason = term()
135%%
136%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
137%%-------------------------------------------------------------------------
138store({transfer_disk_log,TransferDiskLog}, ConfigList)
139  when is_list(TransferDiskLog) ->
140    case create_disk_log(TransferDiskLog,
141			 transfer_disk_log_size, ConfigList) of
142	{ok,TransferDB} ->
143	    {ok,{transfer_disk_log,TransferDB}};
144	{error,Reason} ->
145	    {error,Reason}
146    end;
147store({transfer_disk_log,TransferLog}, _) ->
148    {error, {wrong_type, {transfer_disk_log, TransferLog}}};
149store({security_disk_log,SecurityDiskLog},ConfigList)
150  when is_list(SecurityDiskLog) ->
151    case create_disk_log(SecurityDiskLog,
152			 security_disk_log_size, ConfigList) of
153	{ok,SecurityDB} ->
154	    {ok,{security_disk_log,SecurityDB}};
155	{error,Reason} ->
156	    {error,Reason}
157    end;
158store({security_disk_log, SecurityLog}, _) ->
159    {error, {wrong_type, {security_disk_log, SecurityLog}}};
160
161store({error_disk_log,ErrorDiskLog},ConfigList) when is_list(ErrorDiskLog) ->
162    case create_disk_log(ErrorDiskLog, error_disk_log_size, ConfigList) of
163	{ok,ErrorDB} ->
164	    {ok,{error_disk_log,ErrorDB}};
165	{error,Reason} ->
166	    {error,Reason}
167    end;
168store({error_disk_log,ErrorLog}, _) ->
169    {error, {wrong_type, {error_disk_log, ErrorLog}}};
170store({transfer_disk_log_size, {ByteInt, FileInt}} = Conf, _)
171  when is_integer(ByteInt), is_integer(FileInt)->
172    {ok, Conf};
173store({transfer_disk_log_size, Value}, _) ->
174    {error, {wrong_type, {transfer_disk_log_size, Value}}};
175store({error_disk_log_size, {ByteInt, FileInt}} = Conf, _)
176  when is_integer(ByteInt), is_integer(FileInt)->
177    {ok, Conf};
178store({error_disk_log_size, Value}, _) ->
179    {error, {wrong_type, {error_disk_log_size, Value}}};
180store({security_disk_log_size, {ByteInt, FileInt}} = Conf, _)
181  when is_integer(ByteInt), is_integer(FileInt)->
182    {ok, Conf};
183store({security_disk_log_size, Value}, _) ->
184    {error, {wrong_type, {security_disk_log_size, Value}}};
185store({disk_log_format, Value} = Conf, _) when Value == internal;
186					Value == external ->
187    {ok, Conf};
188store({disk_log_format, Value}, _) ->
189    {error, {wrong_type, {disk_log_format, Value}}}.
190
191%%--------------------------------------------------------------------------
192%% remove(ConfigDb) -> _
193%%
194%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
195%%-------------------------------------------------------------------------
196remove(ConfigDB) ->
197    lists:foreach(fun([DiskLog]) -> close(DiskLog) end,
198		  ets:match(ConfigDB,{transfer_disk_log,'$1'})),
199    lists:foreach(fun([DiskLog]) -> close(DiskLog) end,
200		  ets:match(ConfigDB,{error_disk_log,'$1'})),
201    lists:foreach(fun([DiskLog]) -> close(DiskLog) end,
202		  ets:match(ConfigDB,{security_disk_log,'$1'})),
203    ok.
204
205%%%========================================================================
206%%% Internal functions
207%%%========================================================================
208
209%% transfer_log
210transfer_log(Info, RFC931, AuthUser, Date, StatusCode, Bytes, Format) ->
211    case httpd_log:access_entry(transfer_disk_log, no_transfer_log,
212				Info, RFC931, AuthUser, Date, StatusCode,
213				Bytes) of
214	no_transfer_log ->
215	    ok;
216	{Log, Entry} ->
217    	    write(Log, Entry, Format)
218    end.
219
220
221get_log_format(ConfigDB)->
222    httpd_util:lookup(ConfigDB,disk_log_format,external).
223
224%%----------------------------------------------------------------------
225%% Open or creates the disklogs
226%%----------------------------------------------------------------------
227log_size(ConfigList, Tag) ->
228    proplists:get_value(Tag, ConfigList, {500*1024,8}).
229
230create_disk_log(LogFile, SizeTag, ConfigList) ->
231    Filename = string:strip(LogFile),
232    {MaxBytes, MaxFiles} = log_size(ConfigList, SizeTag),
233    case filename:pathtype(Filename) of
234	absolute ->
235	    create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList);
236	volumerelative ->
237	    create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList);
238	relative ->
239	    case proplists:get_value(server_root,ConfigList) of
240		undefined ->
241		    {error,
242		     ?NICE(Filename++
243			   " is an invalid ErrorLog beacuse ServerRoot "
244			   "is not defined")};
245		ServerRoot ->
246		    AbsoluteFilename = filename:join(ServerRoot,Filename),
247		    create_disk_log(AbsoluteFilename, MaxBytes, MaxFiles,
248				     ConfigList)
249	    end
250    end.
251
252create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList) ->
253    Format = proplists:get_value(disk_log_format, ConfigList, external),
254    open(Filename, MaxBytes, MaxFiles, Format).
255
256
257%%----------------------------------------------------------------------
258%% Function:    open/4
259%% Description: Open a disk log file.
260%% Control which format the disk log will be in. The external file
261%% format is used as default since that format was used by older
262%% implementations of inets.
263%%
264%% When the internal disk log format is used, we will do some extra
265%% controls. If the files are valid, try to repair them and if
266%% thats not possible, truncate.
267%%----------------------------------------------------------------------
268
269open(Filename, MaxBytes, MaxFiles, internal) ->
270    Opt0 = {format, internal},
271    Opts1 = [Opt0, {repair, true}],
272    Opts2 = [Opt0, {repair, truncate}],
273    open1(Filename, MaxBytes, MaxFiles, Opts1, Opts2);
274open(Filename, MaxBytes, MaxFiles, _) ->
275    Opts = [{format, external}],
276    open1(Filename, MaxBytes, MaxFiles, Opts, Opts).
277
278open1(Filename, MaxBytes, MaxFiles, Opts1, Opts2) ->
279    Opts0 = [{name, Filename}, {file, Filename}, {type, wrap}],
280    case open2(Opts0 ++ Opts1, Opts0 ++ Opts2, {MaxBytes, MaxFiles}) of
281        {ok, LogDB} ->
282            {ok, LogDB};
283        {repaired, LogDB, {recovered, _}, {badbytes, _}} ->
284            {ok, LogDB};
285        {error, Reason} ->
286            {error,
287             ?NICE("Can't create " ++ Filename ++
288                   lists:flatten(io_lib:format(", ~p",[Reason])))}
289    end.
290
291open2(Opts1, Opts2, Size) ->
292    case disk_log:open(Opts1) of
293        {error, {badarg, size}} ->
294            %% File did not exist, add the size option and try again
295            disk_log:open([{size, Size} | Opts1]);
296        {error, {Reason, _}} when
297                Reason == not_a_log_file;
298                Reason == invalid_index_file ->
299            %% File was corrupt, add the truncate option and try again
300            disk_log:open([{size, Size} | Opts2]);
301        Else ->
302            Else
303    end.
304
305
306%%----------------------------------------------------------------------
307%% Actually writes the entry to the disk_log. If the log is an
308%% internal disk_log write it with log otherwise with blog.
309%%----------------------------------------------------------------------
310write(Log, Entry, internal) ->
311    disk_log:log(Log, list_to_binary(Entry));
312
313write(Log, Entry, _) ->
314    disk_log:blog(Log, list_to_binary(Entry)).
315
316%% Close the log file
317close(Log) ->
318    disk_log:close(Log).
319
320auth_user(Data) ->
321    case proplists:get_value(remote_user,Data) of
322	undefined ->
323	    "-";
324	RemoteUser ->
325	    RemoteUser
326    end.
327%% log_internal_info
328
329log_internal_info(_, _,[]) ->
330    ok;
331log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) ->
332    error_log(Info,Date,Reason),
333    log_internal_info(Info,Date,Rest);
334log_internal_info(Info,Date,[_|Rest]) ->
335    log_internal_info(Info,Date,Rest).
336
337