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 httpd_util:flatlength(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 _ -> 290 {error, ?NICE("Can't create "++Filename)} 291 end. 292 293open2(Opts1, Opts2, Size) -> 294 case disk_log:open(Opts1) of 295 {error, {badarg, size}} -> 296 %% File did not exist, add the size option and try again 297 disk_log:open([{size, Size} | Opts1]); 298 {error, {Reason, _}} when 299 Reason == not_a_log_file; 300 Reason == invalid_index_file -> 301 %% File was corrupt, add the truncate option and try again 302 disk_log:open([{size, Size} | Opts2]); 303 Else -> 304 Else 305 end. 306 307 308%%---------------------------------------------------------------------- 309%% Actually writes the entry to the disk_log. If the log is an 310%% internal disk_log write it with log otherwise with blog. 311%%---------------------------------------------------------------------- 312write(Log, Entry, internal) -> 313 disk_log:log(Log, list_to_binary(Entry)); 314 315write(Log, Entry, _) -> 316 disk_log:blog(Log, list_to_binary(Entry)). 317 318%% Close the log file 319close(Log) -> 320 disk_log:close(Log). 321 322auth_user(Data) -> 323 case proplists:get_value(remote_user,Data) of 324 undefined -> 325 "-"; 326 RemoteUser -> 327 RemoteUser 328 end. 329%% log_internal_info 330 331log_internal_info(_, _,[]) -> 332 ok; 333log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) -> 334 error_log(Info,Date,Reason), 335 log_internal_info(Info,Date,Rest); 336log_internal_info(Info,Date,[_|Rest]) -> 337 log_internal_info(Info,Date,Rest). 338 339