DEADSOFTWARE

added `utils.formatstrf()`; added `e_LogWritefln()`; made logging system slightly...
[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_WriteLog (TextLine: String; RecordCategory: TRecordCategory; WriteTime: Boolean=True);
36 function DecodeIPV4 (ip: LongWord): string;
38 // start Write/WriteLn driver. it will write everything to cbuf.
39 procedure e_InitWritelnDriver ();
41 procedure e_LogWritefln (const fmt: AnsiString; args: array of const; category: TRecordCategory=MSG_NOTIFY; writeTime: Boolean=true);
44 var
45 e_WriteToStdOut: Boolean = False;
48 implementation
50 uses
51 conbuf, utils;
53 var
54 FirstRecord: Boolean;
55 FileName: String;
56 driverInited: Boolean = false;
59 function DecodeIPV4 (ip: LongWord): string;
60 begin
61 Result := Format('%d.%d.%d.%d', [ip and $FF, (ip shr 8) and $FF, (ip shr 16) and $FF, (ip shr 24)]);
62 end;
65 procedure e_WriteLog (TextLine: String; RecordCategory: TRecordCategory; WriteTime: Boolean=True);
66 begin
67 e_LogWritefln('%s', [TextLine], RecordCategory, WriteTime);
68 end;
69 (*
70 var
71 LogFile: TextFile;
72 Prefix: ShortString = '';
73 OutStr: String;
74 begin
75 if driverInited and (length(TextLine) > 0) then
76 begin
77 case RecordCategory of
78 MSG_FATALERROR: write('FATAL: ');
79 MSG_WARNING: write('WARNING: ');
80 end;
81 writeln(TextLine);
82 end;
84 if FileName = '' then Exit;
86 Assign(LogFile, FileName);
87 try
88 if FileExists(FileName) then
89 Append(LogFile)
90 else
91 Rewrite(LogFile);
92 try
93 if FirstRecord then
94 begin
95 Writeln(LogFile, '--- Log started at '+TimeToStr(Time)+' ---');
96 FirstRecord := False;
97 end;
98 case RecordCategory of
99 MSG_FATALERROR: Prefix := '!!!';
100 MSG_WARNING: Prefix := '! ';
101 MSG_NOTIFY: Prefix := '***';
102 end;
103 if WriteTime then
104 OutStr := '['+TimeToStr(Time)+'] '+Prefix+' '+TextLine
105 else
106 OutStr := Prefix+' '+TextLine;
107 Writeln(LogFile, OutStr);
108 if e_WriteToStdOut then
109 Writeln(OutStr);
110 finally
111 Close(LogFile);
112 end;
113 except // sorry
114 end;
115 end;
116 *)
119 // returns formatted string if `writerCB` is `nil`, empty string otherwise
120 //function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
121 //TFormatStrFCallback = procedure (constref buf; len: SizeUInt);
123 procedure conwriter (constref buf; len: SizeUInt);
124 var
125 ss: ShortString;
126 slen: Integer;
127 b: PByte;
128 begin
129 if (len < 1) then exit;
130 b := PByte(@buf);
131 while (len > 0) do
132 begin
133 if (len > 255) then slen := 255 else slen := Integer(len);
134 Move(b^, ss[1], len);
135 ss[0] := AnsiChar(slen);
136 write(ss);
137 b += slen;
138 len -= slen;
139 end;
140 end;
143 var
144 xlogFile: TextFile;
145 xlogFileOpened: Boolean = false;
146 xlogPrefix: AnsiString;
147 xlogLastWasEOL: Boolean = false;
148 xlogWantSpace: Boolean = false;
150 procedure logwriter (constref buf; len: SizeUInt);
151 var
152 ss: ShortString;
153 slen: Integer;
154 b: PByte;
155 begin
156 if (len < 1) then exit;
157 b := PByte(@buf);
158 if xlogLastWasEOL then
159 begin
160 write(xlogFile, xlogPrefix);
161 xlogLastWasEOL := false;
162 xlogWantSpace := true;
163 end;
164 while (len > 0) do
165 begin
166 slen := 0;
167 while (slen < len) and (slen < 255) and (b[slen] <> 13) and (b[slen] <> 10) do Inc(slen);
168 // print string
169 if (slen > 0) then
170 begin
171 if xlogWantSpace then begin write(xlogFile, ' '); xlogWantSpace := false; end;
172 Move(b^, ss[1], len);
173 ss[0] := AnsiChar(slen);
174 write(xlogFile, ss);
175 b += slen;
176 len -= slen;
177 continue;
178 end;
179 // process newline
180 if (len > 0) and ((b[0] = 13) or (b[0] = 10)) then
181 begin
182 if (len > 1) and (b[0] = 13) and (b[1] = 10) then
183 begin
184 len -= 2;
185 b += 2;
186 end
187 else
188 begin
189 len -= 1;
190 b += 1;
191 end;
192 xlogLastWasEOL := false;
193 writeln(xlogFile, '');
194 write(xlogFile, xlogPrefix);
195 end;
196 end;
197 end;
200 procedure e_LogWritefln (const fmt: AnsiString; args: array of const; category: TRecordCategory=MSG_NOTIFY; writeTime: Boolean=true);
202 procedure xwrite (const s: AnsiString);
203 begin
204 if (Length(s) = 0) then exit;
205 logwriter(PAnsiChar(s)^, Length(s));
206 end;
208 begin
209 if driverInited and (length(fmt) > 0) then
210 begin
211 case category of
212 MSG_FATALERROR: write('FATAL: ');
213 MSG_WARNING: write('WARNING: ');
214 end;
215 formatstrf(fmt, args, conwriter);
216 writeln;
217 end;
219 if (FileName = '') then exit;
221 if not xlogFileOpened then
222 begin
223 AssignFile(xlogFile, FileName);
224 try
225 if FileExists(FileName) then Append(xlogFile) else Rewrite(xlogFile);
226 xlogFileOpened := true;
227 except // sorry
228 exit;
229 end;
230 end;
232 if FirstRecord then
233 begin
234 writeln(xlogFile, '--- Log started at ', TimeToStr(Time), ' ---');
235 FirstRecord := false;
236 end;
238 xlogPrefix := '';
239 if writeTime then begin xlogPrefix += '['; xlogPrefix += TimeToStr(Time); xlogPrefix += '] '; end;
240 case category of
241 MSG_FATALERROR: xlogPrefix += '!!!';
242 MSG_WARNING: xlogPrefix += '! ';
243 MSG_NOTIFY: xlogPrefix += '***';
244 end;
245 xlogLastWasEOL := true; // to output prefix
246 xlogWantSpace := true; // after prefix
247 formatstrf(fmt, args, logwriter);
248 if not xlogLastWasEOL then writeln(xlogFile, '') else writeln(xlogFile, xlogPrefix);
250 //if fopened then CloseFile(xlogFile);
251 end;
254 procedure e_InitLog (fFileName: String; fWriteMode: TWriteMode);
255 begin
256 if xlogFileOpened then CloseFile(xlogFile);
257 xlogFileOpened := false;
258 FileName := fFileName;
259 if (fWriteMode = WM_NEWFILE) then
260 begin
261 try
262 if FileExists(FileName) then DeleteFile(FileName);
263 except // sorry
264 end;
265 end;
266 FirstRecord := true;
267 end;
270 procedure e_DeinitLog ();
271 begin
272 if xlogFileOpened then CloseFile(xlogFile);
273 xlogFileOpened := false;
274 end;
277 // ////////////////////////////////////////////////////////////////////////// //
278 (* Write/WriteLn driver *)
279 //
280 // control codes:
281 // CR, LF, BS
282 // TAB: tab space = 4
283 //
284 // userData[1]: current x (for tabs)
285 // userData[2]: #13 was eaten, we should skip next #10
286 //
287 type
288 TDevFunc = function (var f: TTextRec): Integer;
290 const
291 udX = 1;
292 udWasCR = 2;
295 procedure ProcessOutput (var tf: TTextRec; buf: PChar; count: Integer);
296 var
297 wcr: Boolean;
298 ep: PChar;
299 f, x: Integer;
300 ch: Char;
301 begin
302 x := tf.userData[udX];
303 wcr := (tf.userData[udWasCR] <> 0);
304 while count > 0 do
305 begin
306 // look for some special char
307 ep := buf;
308 f := 0;
309 while f < count do
310 begin
311 ch := ep^;
312 if (ch = #13) or (ch = #10) or (ch = #9) or (ch = #8) then break;
313 Inc(ep);
314 Inc(f);
315 {$IFDEF CBLOG}
316 write(stderr, ch);
317 {$ENDIF}
318 end;
319 if f > 0 then
320 begin
321 wcr := false;
322 cbufPutChars(buf, f);
323 Inc(buf, f);
324 Dec(count, f);
325 Inc(x, f);
326 continue;
327 end;
328 // process special chars
329 ch := buf^;
330 Inc(buf);
331 Dec(count);
332 // tab
333 if ch = #9 then
334 begin
335 {$IFDEF CBLOG}
336 write(stderr, ch);
337 {$ENDIF}
338 repeat
339 cbufPut(' ');
340 Inc(x);
341 until (x mod 4) = 0;
342 continue;
343 end;
344 // cr, lf
345 if (ch = #13) or (ch = #10) then
346 begin
347 {$IFDEF CBLOG}
348 writeln(stderr);
349 {$ENDIF}
350 if not wcr or (ch <> #10) then
351 begin
352 wcr := (ch = #13);
353 x := 0;
354 cbufPut(#10);
355 end;
356 continue;
357 end;
358 end;
359 tf.userData[udX] := x;
360 tf.userData[udWasCR] := ord(wcr);
361 end;
364 function DevOpen (var f: TTextRec): Integer;
365 begin
366 f.userData[udX] := 0;
367 f.userData[udWasCR] := 0;
368 f.bufPos := 0;
369 f.bufEnd := 0;
370 result := 0;
371 end;
373 function DevInOut (var f: TTextRec): Integer;
374 var
375 buf: PChar;
376 sz: Integer;
377 begin
378 result := 0;
379 buf := Pointer(f.BufPtr);
380 sz := f.BufPos;
381 if sz > 0 then ProcessOutput(f, buf, sz);
382 f.bufPos := 0;
383 f.bufEnd := 0;
384 end;
386 function DevFlush (var f: TTextRec): Integer;
387 begin
388 result := DevInOut(f);
389 end;
391 function DevClose (var f: TTextRec): Integer;
392 begin
393 result := 0;
394 end;
397 procedure e_InitWritelnDriver ();
398 begin
399 if not driverInited then
400 begin
401 driverInited := true;
402 with TTextRec(output) do
403 begin
404 Mode := fmClosed;
405 if BufPtr = nil then
406 begin
407 BufSize := SizeOf(Buffer);
408 BufPtr := @Buffer;
409 end;
410 OpenFunc := @DevOpen;
411 InOutFunc := @DevInOut;
412 FlushFunc := @DevFlush;
413 CloseFunc := @DevClose;
414 Name[0] := #0;
415 end;
416 Rewrite(output);
417 end;
418 end;
421 begin
422 //e_InitWritelnDriver();
423 end.