1%% ``Licensed under the Apache License, Version 2.0 (the "License"); 2%% you may not use this file except in compliance with the License. 3%% You may obtain a copy of the License at 4%% 5%% http://www.apache.org/licenses/LICENSE-2.0 6%% 7%% Unless required by applicable law or agreed to in writing, software 8%% distributed under the License is distributed on an "AS IS" BASIS, 9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10%% See the License for the specific language governing permissions and 11%% limitations under the License. 12%% 13%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. 14%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings 15%% AB. All Rights Reserved.'' 16%% 17%% $Id: mod_disk_log.erl,v 1.1 2008/12/17 09:53:35 mikpe Exp $ 18%% 19-module(mod_disk_log). 20-export([do/1,error_log/5,security_log/2,load/2,store/2,remove/1]). 21 22-export([report_error/2]). 23 24-define(VMODULE,"DISK_LOG"). 25-include("httpd_verbosity.hrl"). 26 27-include("httpd.hrl"). 28 29%% do 30 31do(Info) -> 32 AuthUser = auth_user(Info#mod.data), 33 Date = custom_date(), 34 log_internal_info(Info,Date,Info#mod.data), 35 LogFormat = get_log_format(Info#mod.config_db), 36 case httpd_util:key1search(Info#mod.data,status) of 37 %% A status code has been generated! 38 {StatusCode,PhraseArgs,Reason} -> 39 transfer_log(Info, "-", AuthUser, Date, StatusCode, 0, LogFormat), 40 if 41 StatusCode >= 400 -> 42 error_log(Info, Date, Reason, LogFormat); 43 true -> 44 not_an_error 45 end, 46 {proceed,Info#mod.data}; 47 %% No status code has been generated! 48 undefined -> 49 case httpd_util:key1search(Info#mod.data,response) of 50 {already_sent,StatusCode,Size} -> 51 transfer_log(Info, "-", AuthUser, Date, StatusCode, 52 Size, LogFormat), 53 {proceed,Info#mod.data}; 54 55 {response, Head, Body} -> 56 Size = httpd_util:key1search(Head, content_length, 0), 57 Code = httpd_util:key1search(Head, code, 200), 58 transfer_log(Info, "-", AuthUser, Date, Code, 59 Size, LogFormat), 60 {proceed,Info#mod.data}; 61 62 {StatusCode,Response} -> 63 transfer_log(Info, "-", AuthUser, Date, 200, 64 httpd_util:flatlength(Response), LogFormat), 65 {proceed,Info#mod.data}; 66 undefined -> 67 transfer_log(Info, "-", AuthUser, Date, 200, 68 0, LogFormat), 69 {proceed,Info#mod.data} 70 end 71 end. 72 73custom_date() -> 74 LocalTime = calendar:local_time(), 75 UniversalTime = calendar:universal_time(), 76 Minutes = round(diff_in_minutes(LocalTime,UniversalTime)), 77 {{YYYY,MM,DD},{Hour,Min,Sec}} = LocalTime, 78 Date = 79 io_lib:format("~.2.0w/~.3s/~.4w:~.2.0w:~.2.0w:~.2.0w ~c~.2.0w~.2.0w", 80 [DD,httpd_util:month(MM),YYYY,Hour,Min,Sec,sign(Minutes), 81 abs(Minutes) div 60,abs(Minutes) rem 60]), 82 lists:flatten(Date). 83 84diff_in_minutes(L,U) -> 85 (calendar:datetime_to_gregorian_seconds(L) - 86 calendar:datetime_to_gregorian_seconds(U))/60. 87 88sign(Minutes) when Minutes > 0 -> 89 $+; 90sign(Minutes) -> 91 $-. 92 93auth_user(Data) -> 94 case httpd_util:key1search(Data,remote_user) of 95 undefined -> 96 "-"; 97 RemoteUser -> 98 RemoteUser 99 end. 100 101%% log_internal_info 102 103log_internal_info(Info,Date,[]) -> 104 ok; 105log_internal_info(Info,Date,[{internal_info,Reason}|Rest]) -> 106 Format = get_log_format(Info#mod.config_db), 107 error_log(Info,Date,Reason,Format), 108 log_internal_info(Info,Date,Rest); 109log_internal_info(Info,Date,[_|Rest]) -> 110 log_internal_info(Info,Date,Rest). 111 112 113%% transfer_log 114 115transfer_log(Info,RFC931,AuthUser,Date,StatusCode,Bytes,Format) -> 116 case httpd_util:lookup(Info#mod.config_db,transfer_disk_log) of 117 undefined -> 118 no_transfer_log; 119 TransferDiskLog -> 120 {PortNumber,RemoteHost}=(Info#mod.init_data)#init_data.peername, 121 Entry = io_lib:format("~s ~s ~s [~s] \"~s\" ~w ~w~n", 122 [RemoteHost,RFC931,AuthUser,Date, 123 Info#mod.request_line,StatusCode,Bytes]), 124 write(TransferDiskLog, Entry, Format) 125 end. 126 127 128%% error_log 129 130error_log(Info, Date, Reason, Format) -> 131 Format=get_log_format(Info#mod.config_db), 132 case httpd_util:lookup(Info#mod.config_db,error_disk_log) of 133 undefined -> 134 no_error_log; 135 ErrorDiskLog -> 136 {PortNumber,RemoteHost}=(Info#mod.init_data)#init_data.peername, 137 Entry = 138 io_lib:format("[~s] access to ~s failed for ~s, reason: ~p~n", 139 [Date, Info#mod.request_uri, 140 RemoteHost, Reason]), 141 write(ErrorDiskLog, Entry, Format) 142 end. 143 144error_log(SocketType, Socket, ConfigDB, {PortNumber, RemoteHost}, Reason) -> 145 Format = get_log_format(ConfigDB), 146 case httpd_util:lookup(ConfigDB,error_disk_log) of 147 undefined -> 148 no_error_log; 149 ErrorDiskLog -> 150 Date = custom_date(), 151 Entry = 152 io_lib:format("[~s] server crash for ~s, reason: ~p~n", 153 [Date,RemoteHost,Reason]), 154 write(ErrorDiskLog, Entry, Format), 155 ok 156 end. 157 158 159%% security_log 160 161security_log(ConfigDB, Event) -> 162 Format = get_log_format(ConfigDB), 163 case httpd_util:lookup(ConfigDB,security_disk_log) of 164 undefined -> 165 no_error_log; 166 DiskLog -> 167 Date = custom_date(), 168 Entry = io_lib:format("[~s] ~s ~n", [Date, Event]), 169 write(DiskLog, Entry, Format), 170 ok 171 end. 172 173report_error(ConfigDB, Error) -> 174 Format = get_log_format(ConfigDB), 175 case httpd_util:lookup(ConfigDB, error_disk_log) of 176 undefined -> 177 no_error_log; 178 ErrorDiskLog -> 179 Date = custom_date(), 180 Entry = io_lib:format("[~s] reporting error: ~s",[Date,Error]), 181 write(ErrorDiskLog, Entry, Format), 182 ok 183 end. 184 185%%---------------------------------------------------------------------- 186%% Get the current format of the disklog 187%%---------------------------------------------------------------------- 188get_log_format(ConfigDB)-> 189 httpd_util:lookup(ConfigDB,disk_log_format,external). 190 191 192%% 193%% Configuration 194%% 195 196%% load 197 198load([$T,$r,$a,$n,$s,$f,$e,$r,$D,$i,$s,$k,$L,$o,$g,$S,$i,$z,$e,$ | 199 TransferDiskLogSize],[]) -> 200 case regexp:split(TransferDiskLogSize," ") of 201 {ok,[MaxBytes,MaxFiles]} -> 202 case httpd_conf:make_integer(MaxBytes) of 203 {ok,MaxBytesInteger} -> 204 case httpd_conf:make_integer(MaxFiles) of 205 {ok,MaxFilesInteger} -> 206 {ok,[],{transfer_disk_log_size, 207 {MaxBytesInteger,MaxFilesInteger}}}; 208 {error,_} -> 209 {error, 210 ?NICE(httpd_conf:clean(TransferDiskLogSize)++ 211 " is an invalid TransferDiskLogSize")} 212 end; 213 {error,_} -> 214 {error,?NICE(httpd_conf:clean(TransferDiskLogSize)++ 215 " is an invalid TransferDiskLogSize")} 216 end 217 end; 218load([$T,$r,$a,$n,$s,$f,$e,$r,$D,$i,$s,$k,$L,$o,$g,$ |TransferDiskLog],[]) -> 219 {ok,[],{transfer_disk_log,httpd_conf:clean(TransferDiskLog)}}; 220 221load([$E,$r,$r,$o,$r,$D,$i,$s,$k,$L,$o,$g,$S,$i,$z,$e,$ | ErrorDiskLogSize],[]) -> 222 case regexp:split(ErrorDiskLogSize," ") of 223 {ok,[MaxBytes,MaxFiles]} -> 224 case httpd_conf:make_integer(MaxBytes) of 225 {ok,MaxBytesInteger} -> 226 case httpd_conf:make_integer(MaxFiles) of 227 {ok,MaxFilesInteger} -> 228 {ok,[],{error_disk_log_size, 229 {MaxBytesInteger,MaxFilesInteger}}}; 230 {error,_} -> 231 {error,?NICE(httpd_conf:clean(ErrorDiskLogSize)++ 232 " is an invalid ErrorDiskLogSize")} 233 end; 234 {error,_} -> 235 {error,?NICE(httpd_conf:clean(ErrorDiskLogSize)++ 236 " is an invalid ErrorDiskLogSize")} 237 end 238 end; 239load([$E,$r,$r,$o,$r,$D,$i,$s,$k,$L,$o,$g,$ |ErrorDiskLog],[]) -> 240 {ok, [], {error_disk_log, httpd_conf:clean(ErrorDiskLog)}}; 241 242load([$S,$e,$c,$u,$r,$i,$t,$y,$D,$i,$s,$k,$L,$o,$g,$S,$i,$z,$e,$ |SecurityDiskLogSize],[]) -> 243 case regexp:split(SecurityDiskLogSize, " ") of 244 {ok, [MaxBytes, MaxFiles]} -> 245 case httpd_conf:make_integer(MaxBytes) of 246 {ok, MaxBytesInteger} -> 247 case httpd_conf:make_integer(MaxFiles) of 248 {ok, MaxFilesInteger} -> 249 {ok, [], {security_disk_log_size, 250 {MaxBytesInteger, MaxFilesInteger}}}; 251 {error,_} -> 252 {error, ?NICE(httpd_conf:clean(SecurityDiskLogSize)++ 253 " is an invalid SecurityDiskLogSize")} 254 end; 255 {error, _} -> 256 {error, ?NICE(httpd_conf:clean(SecurityDiskLogSize)++ 257 " is an invalid SecurityDiskLogSize")} 258 end 259 end; 260load([$S,$e,$c,$u,$r,$i,$t,$y,$D,$i,$s,$k,$L,$o,$g,$ |SecurityDiskLog],[]) -> 261 {ok, [], {security_disk_log, httpd_conf:clean(SecurityDiskLog)}}; 262 263load([$D,$i,$s,$k,$L,$o,$g,$F,$o,$r,$m,$a,$t,$ |Format],[]) -> 264 case httpd_conf:clean(Format) of 265 "internal" -> 266 {ok, [], {disk_log_format,internal}}; 267 "external" -> 268 {ok, [], {disk_log_format,external}}; 269 _Default -> 270 {ok, [], {disk_log_format,external}} 271 end. 272 273%% store 274 275store({transfer_disk_log,TransferDiskLog},ConfigList) -> 276 case create_disk_log(TransferDiskLog, transfer_disk_log_size, ConfigList) of 277 {ok,TransferDB} -> 278 {ok,{transfer_disk_log,TransferDB}}; 279 {error,Reason} -> 280 {error,Reason} 281 end; 282store({security_disk_log,SecurityDiskLog},ConfigList) -> 283 case create_disk_log(SecurityDiskLog, security_disk_log_size, ConfigList) of 284 {ok,SecurityDB} -> 285 {ok,{security_disk_log,SecurityDB}}; 286 {error,Reason} -> 287 {error,Reason} 288 end; 289store({error_disk_log,ErrorDiskLog},ConfigList) -> 290 case create_disk_log(ErrorDiskLog, error_disk_log_size, ConfigList) of 291 {ok,ErrorDB} -> 292 {ok,{error_disk_log,ErrorDB}}; 293 {error,Reason} -> 294 {error,Reason} 295 end. 296 297 298%%---------------------------------------------------------------------- 299%% Open or creates the disklogs 300%%---------------------------------------------------------------------- 301log_size(ConfigList, Tag) -> 302 httpd_util:key1search(ConfigList, Tag, {500*1024,8}). 303 304create_disk_log(LogFile, SizeTag, ConfigList) -> 305 Filename = httpd_conf:clean(LogFile), 306 {MaxBytes, MaxFiles} = log_size(ConfigList, SizeTag), 307 case filename:pathtype(Filename) of 308 absolute -> 309 create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList); 310 volumerelative -> 311 create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList); 312 relative -> 313 case httpd_util:key1search(ConfigList,server_root) of 314 undefined -> 315 {error, 316 ?NICE(Filename++ 317 " is an invalid ErrorLog beacuse ServerRoot is not defined")}; 318 ServerRoot -> 319 AbsoluteFilename = filename:join(ServerRoot,Filename), 320 create_disk_log(AbsoluteFilename, MaxBytes, MaxFiles, 321 ConfigList) 322 end 323 end. 324 325create_disk_log(Filename, MaxBytes, MaxFiles, ConfigList) -> 326 Format = httpd_util:key1search(ConfigList, disk_log_format, external), 327 open(Filename, MaxBytes, MaxFiles, Format). 328 329 330 331%% remove 332remove(ConfigDB) -> 333 lists:foreach(fun([DiskLog]) -> close(DiskLog) end, 334 ets:match(ConfigDB,{transfer_disk_log,'$1'})), 335 lists:foreach(fun([DiskLog]) -> close(DiskLog) end, 336 ets:match(ConfigDB,{error_disk_log,'$1'})), 337 ok. 338 339 340%% 341%% Some disk_log wrapper functions: 342%% 343 344%%---------------------------------------------------------------------- 345%% Function: open/4 346%% Description: Open a disk log file. 347%% Control which format the disk log will be in. The external file 348%% format is used as default since that format was used by older 349%% implementations of inets. 350%% 351%% When the internal disk log format is used, we will do some extra 352%% controls. If the files are valid, try to repair them and if 353%% thats not possible, truncate. 354%%---------------------------------------------------------------------- 355 356open(Filename, MaxBytes, MaxFiles, internal) -> 357 Opts = [{format, internal}, {repair, truncate}], 358 open1(Filename, MaxBytes, MaxFiles, Opts); 359open(Filename, MaxBytes, MaxFiles, _) -> 360 Opts = [{format, external}], 361 open1(Filename, MaxBytes, MaxFiles, Opts). 362 363open1(Filename, MaxBytes, MaxFiles, Opts0) -> 364 Opts1 = [{name, Filename}, {file, Filename}, {type, wrap}] ++ Opts0, 365 case open2(Opts1, {MaxBytes, MaxFiles}) of 366 {ok, LogDB} -> 367 {ok, LogDB}; 368 {error, Reason} -> 369 ?vlog("failed opening disk log with args:" 370 "~n Filename: ~p" 371 "~n MaxBytes: ~p" 372 "~n MaxFiles: ~p" 373 "~n Opts0: ~p" 374 "~nfor reason:" 375 "~n ~p", [Filename, MaxBytes, MaxFiles, Opts0, Reason]), 376 {error, 377 ?NICE("Can't create " ++ Filename ++ 378 lists:flatten(io_lib:format(", ~p",[Reason])))}; 379 _ -> 380 {error, ?NICE("Can't create "++Filename)} 381 end. 382 383open2(Opts, Size) -> 384 case disk_log:open(Opts) of 385 {error, {badarg, size}} -> 386 %% File did not exist, add the size option and try again 387 disk_log:open([{size, Size} | Opts]); 388 Else -> 389 Else 390 end. 391 392 393%%---------------------------------------------------------------------- 394%% Actually writes the entry to the disk_log. If the log is an 395%% internal disk_log write it with log otherwise with blog. 396%%---------------------------------------------------------------------- 397write(Log, Entry, internal) -> 398 disk_log:log(Log, Entry); 399 400write(Log, Entry, _) -> 401 disk_log:blog(Log, Entry). 402 403%% Close the log file 404close(Log) -> 405 disk_log:close(Log). 406