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_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, load/2, 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%% load(Line, Context) -> eof | ok | {ok, NewContext} | 130%% {ok, NewContext, Directive} | 131%% {ok, NewContext, DirectiveList} | {error, Reason} 132%% Line = string() 133%% Context = NewContext = DirectiveList = [Directive] 134%% Directive = {DirectiveKey , DirectiveValue} 135%% DirectiveKey = DirectiveValue = term() 136%% Reason = term() 137%% 138%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS 139%%------------------------------------------------------------------------- 140load("TransferDiskLogSize " ++ TransferDiskLogSize, []) -> 141 try re:split(TransferDiskLogSize, " ", [{return, list}]) of 142 [MaxBytes, MaxFiles] -> 143 case make_integer(MaxBytes) of 144 {ok,MaxBytesInteger} -> 145 case make_integer(MaxFiles) of 146 {ok,MaxFilesInteger} -> 147 {ok,[],{transfer_disk_log_size, 148 {MaxBytesInteger,MaxFilesInteger}}}; 149 {error,_} -> 150 {error, 151 ?NICE(string:strip(TransferDiskLogSize)++ 152 " is an invalid TransferDiskLogSize")} 153 end; 154 _ -> 155 {error,?NICE(string:strip(TransferDiskLogSize)++ 156 " is an invalid TransferDiskLogSize")} 157 end 158 catch _:_ -> 159 {error,?NICE(string:strip(TransferDiskLogSize) ++ 160 " is an invalid TransferDiskLogSize")} 161 end; 162load("TransferDiskLog " ++ TransferDiskLog,[]) -> 163 {ok,[],{transfer_disk_log,string:strip(TransferDiskLog)}}; 164 165load("ErrorDiskLogSize " ++ ErrorDiskLogSize, []) -> 166 try re:split(ErrorDiskLogSize," ", [{return, list}]) of 167 [MaxBytes,MaxFiles] -> 168 case make_integer(MaxBytes) of 169 {ok,MaxBytesInteger} -> 170 case make_integer(MaxFiles) of 171 {ok,MaxFilesInteger} -> 172 {ok,[],{error_disk_log_size, 173 {MaxBytesInteger,MaxFilesInteger}}}; 174 {error,_} -> 175 {error,?NICE(string:strip(ErrorDiskLogSize)++ 176 " is an invalid ErrorDiskLogSize")} 177 end; 178 {error,_} -> 179 {error,?NICE(string:strip(ErrorDiskLogSize)++ 180 " is an invalid ErrorDiskLogSize")} 181 end 182 catch _:_ -> 183 {error,?NICE(string:strip(ErrorDiskLogSize) ++ 184 " is an invalid TransferDiskLogSize")} 185 end; 186load("ErrorDiskLog " ++ ErrorDiskLog, []) -> 187 {ok, [], {error_disk_log, string:strip(ErrorDiskLog)}}; 188 189load("SecurityDiskLogSize " ++ SecurityDiskLogSize, []) -> 190 try re:split(SecurityDiskLogSize, " ", [{return, list}]) of 191 [MaxBytes, MaxFiles] -> 192 case make_integer(MaxBytes) of 193 {ok, MaxBytesInteger} -> 194 case make_integer(MaxFiles) of 195 {ok, MaxFilesInteger} -> 196 {ok, [], {security_disk_log_size, 197 {MaxBytesInteger, MaxFilesInteger}}}; 198 {error,_} -> 199 {error, 200 ?NICE(string:strip(SecurityDiskLogSize) ++ 201 " is an invalid SecurityDiskLogSize")} 202 end; 203 {error, _} -> 204 {error, ?NICE(string:strip(SecurityDiskLogSize) ++ 205 " is an invalid SecurityDiskLogSize")} 206 end 207 catch _:_ -> 208 {error,?NICE(string:strip(SecurityDiskLogSize) ++ 209 " is an invalid SecurityDiskLogSize")} 210 end; 211load("SecurityDiskLog " ++ SecurityDiskLog, []) -> 212 {ok, [], {security_disk_log, string:strip(SecurityDiskLog)}}; 213 214load("DiskLogFormat " ++ Format, []) -> 215 case string:strip(Format) of 216 "internal" -> 217 {ok, [], {disk_log_format,internal}}; 218 "external" -> 219 {ok, [], {disk_log_format,external}}; 220 _Default -> 221 {ok, [], {disk_log_format,external}} 222 end. 223 224%%-------------------------------------------------------------------------- 225%% store(Directive, DirectiveList) -> {ok, NewDirective} | 226%% {ok, [NewDirective]} | 227%% {error, Reason} 228%% Directive = {DirectiveKey , DirectiveValue} 229%% DirectiveKey = DirectiveValue = term() 230%% Reason = term() 231%% 232%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS 233%%------------------------------------------------------------------------- 234store({transfer_disk_log,TransferDiskLog}, ConfigList) 235 when is_list(TransferDiskLog) -> 236 case create_disk_log(TransferDiskLog, 237 transfer_disk_log_size, ConfigList) of 238 {ok,TransferDB} -> 239 {ok,{transfer_disk_log,TransferDB}}; 240 {error,Reason} -> 241 {error,Reason} 242 end; 243store({transfer_disk_log,TransferLog}, _) -> 244 {error, {wrong_type, {transfer_disk_log, TransferLog}}}; 245store({security_disk_log,SecurityDiskLog},ConfigList) 246 when is_list(SecurityDiskLog) -> 247 case create_disk_log(SecurityDiskLog, 248 security_disk_log_size, ConfigList) of 249 {ok,SecurityDB} -> 250 {ok,{security_disk_log,SecurityDB}}; 251 {error,Reason} -> 252 {error,Reason} 253 end; 254store({security_disk_log, SecurityLog}, _) -> 255 {error, {wrong_type, {security_disk_log, SecurityLog}}}; 256 257store({error_disk_log,ErrorDiskLog},ConfigList) when is_list(ErrorDiskLog) -> 258 case create_disk_log(ErrorDiskLog, error_disk_log_size, ConfigList) of 259 {ok,ErrorDB} -> 260 {ok,{error_disk_log,ErrorDB}}; 261 {error,Reason} -> 262 {error,Reason} 263 end; 264store({error_disk_log,ErrorLog}, _) -> 265 {error, {wrong_type, {error_disk_log, ErrorLog}}}; 266store({transfer_disk_log_size, {ByteInt, FileInt}} = Conf, _) 267 when is_integer(ByteInt), is_integer(FileInt)-> 268 {ok, Conf}; 269store({transfer_disk_log_size, Value}, _) -> 270 {error, {wrong_type, {transfer_disk_log_size, Value}}}; 271store({error_disk_log_size, {ByteInt, FileInt}} = Conf, _) 272 when is_integer(ByteInt), is_integer(FileInt)-> 273 {ok, Conf}; 274store({error_disk_log_size, Value}, _) -> 275 {error, {wrong_type, {error_disk_log_size, Value}}}; 276store({security_disk_log_size, {ByteInt, FileInt}} = Conf, _) 277 when is_integer(ByteInt), is_integer(FileInt)-> 278 {ok, Conf}; 279store({security_disk_log_size, Value}, _) -> 280 {error, {wrong_type, {security_disk_log_size, Value}}}; 281store({disk_log_format, Value} = Conf, _) when Value == internal; 282 Value == external -> 283 {ok, Conf}; 284store({disk_log_format, Value}, _) -> 285 {error, {wrong_type, {disk_log_format, Value}}}. 286 287%%-------------------------------------------------------------------------- 288%% remove(ConfigDb) -> _ 289%% 290%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS 291%%------------------------------------------------------------------------- 292remove(ConfigDB) -> 293 lists:foreach(fun([DiskLog]) -> close(DiskLog) end, 294 ets:match(ConfigDB,{transfer_disk_log,'$1'})), 295 lists:foreach(fun([DiskLog]) -> close(DiskLog) end, 296 ets:match(ConfigDB,{error_disk_log,'$1'})), 297 lists:foreach(fun([DiskLog]) -> close(DiskLog) end, 298 ets:match(ConfigDB,{security_disk_log,'$1'})), 299 ok. 300 301%%%======================================================================== 302%%% Internal functions 303%%%======================================================================== 304 305%% transfer_log 306transfer_log(Info, RFC931, AuthUser, Date, StatusCode, Bytes, Format) -> 307 case httpd_log:access_entry(transfer_disk_log, no_transfer_log, 308 Info, RFC931, AuthUser, Date, StatusCode, 309 Bytes) of 310 no_transfer_log -> 311 ok; 312 {Log, Entry} -> 313 write(Log, Entry, Format) 314 end. 315 316 317get_log_format(ConfigDB)-> 318 httpd_util:lookup(ConfigDB,disk_log_format,external). 319 320%%---------------------------------------------------------------------- 321%% Open or creates the disklogs 322%%---------------------------------------------------------------------- 323log_size(ConfigList, Tag) -> 324 proplists:get_value(Tag, ConfigList, {500*1024,8}). 325 326create_disk_log(LogFile, SizeTag, ConfigList) -> 327 Filename = string:strip(LogFile), 328 {MaxBytes, MaxFiles} = log_size(ConfigList, SizeTag), 329 case filename:pathtype(Filename) of 330 absolute -> 331 create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList); 332 volumerelative -> 333 create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList); 334 relative -> 335 case proplists:get_value(server_root,ConfigList) of 336 undefined -> 337 {error, 338 ?NICE(Filename++ 339 " is an invalid ErrorLog beacuse ServerRoot " 340 "is not defined")}; 341 ServerRoot -> 342 AbsoluteFilename = filename:join(ServerRoot,Filename), 343 create_disk_log(AbsoluteFilename, MaxBytes, MaxFiles, 344 ConfigList) 345 end 346 end. 347 348create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList) -> 349 Format = proplists:get_value(disk_log_format, ConfigList, external), 350 open(Filename, MaxBytes, MaxFiles, Format). 351 352 353%%---------------------------------------------------------------------- 354%% Function: open/4 355%% Description: Open a disk log file. 356%% Control which format the disk log will be in. The external file 357%% format is used as default since that format was used by older 358%% implementations of inets. 359%% 360%% When the internal disk log format is used, we will do some extra 361%% controls. If the files are valid, try to repair them and if 362%% thats not possible, truncate. 363%%---------------------------------------------------------------------- 364 365open(Filename, MaxBytes, MaxFiles, internal) -> 366 Opt0 = {format, internal}, 367 Opts1 = [Opt0, {repair, true}], 368 Opts2 = [Opt0, {repair, truncate}], 369 open1(Filename, MaxBytes, MaxFiles, Opts1, Opts2); 370open(Filename, MaxBytes, MaxFiles, _) -> 371 Opts = [{format, external}], 372 open1(Filename, MaxBytes, MaxFiles, Opts, Opts). 373 374open1(Filename, MaxBytes, MaxFiles, Opts1, Opts2) -> 375 Opts0 = [{name, Filename}, {file, Filename}, {type, wrap}], 376 case open2(Opts0 ++ Opts1, Opts0 ++ Opts2, {MaxBytes, MaxFiles}) of 377 {ok, LogDB} -> 378 {ok, LogDB}; 379 {repaired, LogDB, {recovered, _}, {badbytes, _}} -> 380 {ok, LogDB}; 381 {error, Reason} -> 382 {error, 383 ?NICE("Can't create " ++ Filename ++ 384 lists:flatten(io_lib:format(", ~p",[Reason])))}; 385 _ -> 386 {error, ?NICE("Can't create "++Filename)} 387 end. 388 389open2(Opts1, Opts2, Size) -> 390 case disk_log:open(Opts1) of 391 {error, {badarg, size}} -> 392 %% File did not exist, add the size option and try again 393 disk_log:open([{size, Size} | Opts1]); 394 {error, {Reason, _}} when 395 Reason == not_a_log_file; 396 Reason == invalid_index_file -> 397 %% File was corrupt, add the truncate option and try again 398 disk_log:open([{size, Size} | Opts2]); 399 Else -> 400 Else 401 end. 402 403 404%%---------------------------------------------------------------------- 405%% Actually writes the entry to the disk_log. If the log is an 406%% internal disk_log write it with log otherwise with blog. 407%%---------------------------------------------------------------------- 408write(Log, Entry, internal) -> 409 disk_log:log(Log, list_to_binary(Entry)); 410 411write(Log, Entry, _) -> 412 disk_log:blog(Log, list_to_binary(Entry)). 413 414%% Close the log file 415close(Log) -> 416 disk_log:close(Log). 417 418auth_user(Data) -> 419 case proplists:get_value(remote_user,Data) of 420 undefined -> 421 "-"; 422 RemoteUser -> 423 RemoteUser 424 end. 425%% log_internal_info 426 427log_internal_info(_, _,[]) -> 428 ok; 429log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) -> 430 error_log(Info,Date,Reason), 431 log_internal_info(Info,Date,Rest); 432log_internal_info(Info,Date,[_|Rest]) -> 433 log_internal_info(Info,Date,Rest). 434 435make_integer(List) -> 436 try list_to_integer(List) of 437 N -> 438 {ok, N} 439 catch 440 _:_ -> 441 {error, {badarg, list_to_integer, List}} 442 end. 443