DEADSOFTWARE

added "--safe-log" cli arg: log writer will be slower, but should not loose log entri...
[d2df-sdl.git] / src / engine / e_log.pas
1 (* Copyright (C) DooM 2D:Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 {$R-}
18 { $DEFINE CBLOG}
19 unit e_log;
21 interface
23 uses
24 SysUtils;
26 type
27 TWriteMode = (WM_NEWFILE, WM_OLDFILE);
28 TRecordCategory = (MSG_FATALERROR, MSG_WARNING, MSG_NOTIFY);
31 procedure e_InitLog (fFileName: String; fWriteMode: TWriteMode);
32 procedure e_DeinitLog ();
34 procedure e_SetSafeSlowLog (slowAndSafe: Boolean);
36 procedure e_WriteLog (TextLine: String; RecordCategory: TRecordCategory; WriteTime: Boolean=True);
38 function DecodeIPV4 (ip: LongWord): string;
40 // start Write/WriteLn driver. it will write everything to cbuf.
41 procedure e_InitWritelnDriver ();
43 procedure e_LogWritefln (const fmt: AnsiString; args: array of const; category: TRecordCategory=MSG_NOTIFY; writeTime: Boolean=true);
46 var
47 e_WriteToStdOut: Boolean = False;
50 implementation
52 uses
53 conbuf, utils;
55 var
56 FirstRecord: Boolean;
57 FileName: String;
58 driverInited: Boolean = false;
61 function DecodeIPV4 (ip: LongWord): string;
62 begin
63 Result := Format('%d.%d.%d.%d', [ip and $FF, (ip shr 8) and $FF, (ip shr 16) and $FF, (ip shr 24)]);
64 end;
67 procedure e_WriteLog (TextLine: String; RecordCategory: TRecordCategory; WriteTime: Boolean=True);
68 begin
69 e_LogWritefln('%s', [TextLine], RecordCategory, WriteTime);
70 end;
71 (*
72 var
73 LogFile: TextFile;
74 Prefix: ShortString = '';
75 OutStr: String;
76 begin
77 if driverInited and (length(TextLine) > 0) then
78 begin
79 case RecordCategory of
80 MSG_FATALERROR: write('FATAL: ');
81 MSG_WARNING: write('WARNING: ');
82 end;
83 writeln(TextLine);
84 end;
86 if FileName = '' then Exit;
88 Assign(LogFile, FileName);
89 try
90 if FileExists(FileName) then
91 Append(LogFile)
92 else
93 Rewrite(LogFile);
94 try
95 if FirstRecord then
96 begin
97 Writeln(LogFile, '--- Log started at '+TimeToStr(Time)+' ---');
98 FirstRecord := False;
99 end;
100 case RecordCategory of
101 MSG_FATALERROR: Prefix := '!!!';
102 MSG_WARNING: Prefix := '! ';
103 MSG_NOTIFY: Prefix := '***';
104 end;
105 if WriteTime then
106 OutStr := '['+TimeToStr(Time)+'] '+Prefix+' '+TextLine
107 else
108 OutStr := Prefix+' '+TextLine;
109 Writeln(LogFile, OutStr);
110 if e_WriteToStdOut then
111 Writeln(OutStr);
112 finally
113 Close(LogFile);
114 end;
115 except // sorry
116 end;
117 end;
118 *)
121 // returns formatted string if `writerCB` is `nil`, empty string otherwise
122 //function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
123 //TFormatStrFCallback = procedure (constref buf; len: SizeUInt);
125 procedure conwriter (constref buf; len: SizeUInt);
126 var
127 ss: ShortString;
128 slen: Integer;
129 b: PByte;
130 begin
131 if (len < 1) then exit;
132 b := PByte(@buf);
133 while (len > 0) do
134 begin
135 if (len > 255) then slen := 255 else slen := Integer(len);
136 Move(b^, ss[1], len);
137 ss[0] := AnsiChar(slen);
138 write(ss);
139 b += slen;
140 len -= slen;
141 end;
142 end;
145 var
146 xlogFile: TextFile;
147 xlogFileOpened: Boolean = false;
148 xlogPrefix: AnsiString;
149 xlogLastWasEOL: Boolean = false;
150 xlogWantSpace: Boolean = false;
151 xlogSlowAndSafe: Boolean = false;
154 procedure e_SetSafeSlowLog (slowAndSafe: Boolean);
155 begin
156 xlogSlowAndSafe := slowAndSafe;
157 if xlogSlowAndSafe and xlogFileOpened then
158 begin
159 CloseFile(xlogFile);
160 xlogFileOpened := false;
161 end;
162 end;
165 procedure logwriter (constref buf; len: SizeUInt);
166 var
167 ss: ShortString;
168 slen: Integer;
169 b: PByte;
170 begin
171 if (len < 1) then exit;
172 b := PByte(@buf);
173 if xlogLastWasEOL then
174 begin
175 write(xlogFile, xlogPrefix);
176 xlogLastWasEOL := false;
177 xlogWantSpace := true;
178 end;
179 while (len > 0) do
180 begin
181 slen := 0;
182 while (slen < len) and (slen < 255) and (b[slen] <> 13) and (b[slen] <> 10) do Inc(slen);
183 // print string
184 if (slen > 0) then
185 begin
186 if xlogWantSpace then begin write(xlogFile, ' '); xlogWantSpace := false; end;
187 Move(b^, ss[1], len);
188 ss[0] := AnsiChar(slen);
189 write(xlogFile, ss);
190 b += slen;
191 len -= slen;
192 continue;
193 end;
194 // process newline
195 if (len > 0) and ((b[0] = 13) or (b[0] = 10)) then
196 begin
197 if (len > 1) and (b[0] = 13) and (b[1] = 10) then
198 begin
199 len -= 2;
200 b += 2;
201 end
202 else
203 begin
204 len -= 1;
205 b += 1;
206 end;
207 xlogLastWasEOL := false;
208 writeln(xlogFile, '');
209 write(xlogFile, xlogPrefix);
210 end;
211 end;
212 end;
215 procedure e_LogWritefln (const fmt: AnsiString; args: array of const; category: TRecordCategory=MSG_NOTIFY; writeTime: Boolean=true);
217 procedure xwrite (const s: AnsiString);
218 begin
219 if (Length(s) = 0) then exit;
220 logwriter(PAnsiChar(s)^, Length(s));
221 end;
223 begin
224 if driverInited and (length(fmt) > 0) then
225 begin
226 case category of
227 MSG_FATALERROR: write('FATAL: ');
228 MSG_WARNING: write('WARNING: ');
229 end;
230 formatstrf(fmt, args, conwriter);
231 writeln;
232 end;
234 if (FileName = '') then exit;
236 if not xlogFileOpened then
237 begin
238 AssignFile(xlogFile, FileName);
239 try
240 if FileExists(FileName) then Append(xlogFile) else Rewrite(xlogFile);
241 xlogFileOpened := true;
242 except // sorry
243 exit;
244 end;
245 end;
247 if FirstRecord then
248 begin
249 writeln(xlogFile, '--- Log started at ', TimeToStr(Time), ' ---');
250 FirstRecord := false;
251 end;
253 xlogPrefix := '';
254 if writeTime then begin xlogPrefix += '['; xlogPrefix += TimeToStr(Time); xlogPrefix += '] '; end;
255 case category of
256 MSG_FATALERROR: xlogPrefix += '!!!';
257 MSG_WARNING: xlogPrefix += '! ';
258 MSG_NOTIFY: xlogPrefix += '***';
259 end;
260 xlogLastWasEOL := true; // to output prefix
261 xlogWantSpace := true; // after prefix
262 formatstrf(fmt, args, logwriter);
263 if not xlogLastWasEOL then writeln(xlogFile, '') else writeln(xlogFile, xlogPrefix);
265 if xlogSlowAndSafe and xlogFileOpened then
266 begin
267 CloseFile(xlogFile);
268 xlogFileOpened := false;
269 end;
271 //if fopened then CloseFile(xlogFile);
272 end;
275 procedure e_InitLog (fFileName: String; fWriteMode: TWriteMode);
276 begin
277 if xlogFileOpened then CloseFile(xlogFile);
278 xlogFileOpened := false;
279 FileName := fFileName;
280 if (fWriteMode = WM_NEWFILE) then
281 begin
282 try
283 if FileExists(FileName) then DeleteFile(FileName);
284 except // sorry
285 end;
286 end;
287 FirstRecord := true;
288 end;
291 procedure e_DeinitLog ();
292 begin
293 if xlogFileOpened then CloseFile(xlogFile);
294 xlogFileOpened := false;
295 end;
298 // ////////////////////////////////////////////////////////////////////////// //
299 (* Write/WriteLn driver *)
300 //
301 // control codes:
302 // CR, LF, BS
303 // TAB: tab space = 4
304 //
305 // userData[1]: current x (for tabs)
306 // userData[2]: #13 was eaten, we should skip next #10
307 //
308 type
309 TDevFunc = function (var f: TTextRec): Integer;
311 const
312 udX = 1;
313 udWasCR = 2;
316 procedure ProcessOutput (var tf: TTextRec; buf: PChar; count: Integer);
317 var
318 wcr: Boolean;
319 ep: PChar;
320 f, x: Integer;
321 ch: Char;
322 begin
323 x := tf.userData[udX];
324 wcr := (tf.userData[udWasCR] <> 0);
325 while count > 0 do
326 begin
327 // look for some special char
328 ep := buf;
329 f := 0;
330 while f < count do
331 begin
332 ch := ep^;
333 if (ch = #13) or (ch = #10) or (ch = #9) or (ch = #8) then break;
334 Inc(ep);
335 Inc(f);
336 {$IFDEF CBLOG}
337 write(stderr, ch);
338 {$ENDIF}
339 end;
340 if f > 0 then
341 begin
342 wcr := false;
343 cbufPutChars(buf, f);
344 Inc(buf, f);
345 Dec(count, f);
346 Inc(x, f);
347 continue;
348 end;
349 // process special chars
350 ch := buf^;
351 Inc(buf);
352 Dec(count);
353 // tab
354 if ch = #9 then
355 begin
356 {$IFDEF CBLOG}
357 write(stderr, ch);
358 {$ENDIF}
359 repeat
360 cbufPut(' ');
361 Inc(x);
362 until (x mod 4) = 0;
363 continue;
364 end;
365 // cr, lf
366 if (ch = #13) or (ch = #10) then
367 begin
368 {$IFDEF CBLOG}
369 writeln(stderr);
370 {$ENDIF}
371 if not wcr or (ch <> #10) then
372 begin
373 wcr := (ch = #13);
374 x := 0;
375 cbufPut(#10);
376 end;
377 continue;
378 end;
379 end;
380 tf.userData[udX] := x;
381 tf.userData[udWasCR] := ord(wcr);
382 end;
385 function DevOpen (var f: TTextRec): Integer;
386 begin
387 f.userData[udX] := 0;
388 f.userData[udWasCR] := 0;
389 f.bufPos := 0;
390 f.bufEnd := 0;
391 result := 0;
392 end;
394 function DevInOut (var f: TTextRec): Integer;
395 var
396 buf: PChar;
397 sz: Integer;
398 begin
399 result := 0;
400 buf := Pointer(f.BufPtr);
401 sz := f.BufPos;
402 if sz > 0 then ProcessOutput(f, buf, sz);
403 f.bufPos := 0;
404 f.bufEnd := 0;
405 end;
407 function DevFlush (var f: TTextRec): Integer;
408 begin
409 result := DevInOut(f);
410 end;
412 function DevClose (var f: TTextRec): Integer;
413 begin
414 result := 0;
415 end;
418 procedure e_InitWritelnDriver ();
419 begin
420 if not driverInited then
421 begin
422 driverInited := true;
423 with TTextRec(output) do
424 begin
425 Mode := fmClosed;
426 if BufPtr = nil then
427 begin
428 BufSize := SizeOf(Buffer);
429 BufPtr := @Buffer;
430 end;
431 OpenFunc := @DevOpen;
432 InOutFunc := @DevInOut;
433 FlushFunc := @DevFlush;
434 CloseFunc := @DevClose;
435 Name[0] := #0;
436 end;
437 Rewrite(output);
438 end;
439 end;
442 begin
443 //e_InitWritelnDriver();
444 end.