1 module gfx.core.log;
2 
3 import core.sync.mutex : Mutex;
4 import std.datetime : SysTime;
5 
6 struct LogTag
7 {
8     string tag;
9     uint mask = 0xffff_ffff;
10 
11     /// add a log entry with this tag
12     void log (in Severity sev, scope lazy string msg) const
13     {
14         .log (sev, mask, tag, msg);
15     }
16 
17     /// add a formatted log entry with this tag
18     void logf (Args...)(in Severity sev, in string fmt, Args args) const
19     {
20         .logf (sev, mask, tag, fmt, args);
21     }
22 
23     /// add a trace log entry with this tag
24     void trace (scope lazy string msg) const
25     {
26         .log (Severity.trace, mask, tag, msg);
27     }
28 
29     /// add a formatted trace log entry with this tag
30     void tracef (Args...)(in string fmt, Args args) const
31     {
32         .logf (Severity.trace, mask, tag, fmt, args);
33     }
34 
35     /// add a debug log entry with this tag
36     void debug_ (scope lazy string msg) const
37     {
38         .log (Severity.debug_, mask, tag, msg);
39     }
40 
41     /// add a formatted debug log entry with this tag
42     void debugf (Args...)(in string fmt, Args args) const
43     {
44         .logf (Severity.debug_, mask, tag, fmt, args);
45     }
46 
47     /// add a info log entry with this tag
48     void info (scope lazy string msg) const
49     {
50         .log (Severity.info, mask, tag, msg);
51     }
52 
53     /// add a formatted info log entry with this tag
54     void infof (Args...)(in string fmt, Args args) const
55     {
56         .logf (Severity.info, mask, tag, fmt, args);
57     }
58 
59     /// add a warning log entry with this tag
60     void warning (scope lazy string msg) const
61     {
62         .log (Severity.warning, mask, tag, msg);
63     }
64 
65     /// add a formatted warning log entry with this tag
66     void warningf (Args...)(in string fmt, Args args) const
67     {
68         .logf (Severity.warning, mask, tag, fmt, args);
69     }
70 
71     /// add a error log entry with this tag
72     void error (scope lazy string msg) const
73     {
74         .log (Severity.error, mask, tag, msg);
75     }
76 
77     /// add a formatted error log entry with this tag
78     void errorf (Args...)(in string fmt, Args args) const
79     {
80         .logf (Severity.error, mask, tag, fmt, args);
81     }
82 }
83 
84 /// Log messages severity
85 enum Severity
86 {
87     ///
88     trace,
89     /// debug messages
90     debug_,
91     ///
92     info,
93     ///
94     warning,
95     ///
96     error,
97 }
98 
99 /// The default severity
100 enum defaultSeverity = Severity.info;
101 
102 /// The default filter mask
103 enum uint defaultMask = 0xffff_ffff;
104 
105 /// add a log entry
106 void log (in Severity sev, in string tag, scope lazy string msg)
107 {
108     s_mutex.lock();
109     scope(exit) s_mutex.unlock();
110 
111     if (cast(int)sev >= cast(int)s_sev) {
112         s_logger.print(s_msgFmt.formatMsg(sev, tag, msg));
113     }
114 }
115 /// ditto
116 void log (in Severity sev, in uint mask, in string tag, scope lazy string msg)
117 {
118     s_mutex.lock();
119     scope(exit) s_mutex.unlock();
120 
121     if (cast(int)sev >= cast(int)s_sev && (mask & s_mask) != 0) {
122         s_logger.print(s_msgFmt.formatMsg(sev, tag, msg));
123     }
124 }
125 /// ditto
126 void logf (Args...) (in Severity sev, in string tag, in string fmt, Args args)
127 {
128     import std.format : format;
129 
130     s_mutex.lock();
131     scope(exit) s_mutex.unlock();
132 
133     if (cast(int)sev >= cast(int)s_sev) {
134         s_logger.print(s_msgFmt.formatMsg(sev, tag, format(fmt, args)));
135     }
136 }
137 /// ditto
138 void logf (Args...) (in Severity sev, in uint mask, in string tag, in string fmt, Args args)
139 {
140     import std.format : format;
141 
142     s_mutex.lock();
143     scope(exit) s_mutex.unlock();
144 
145     if (cast(int)sev >= cast(int)s_sev && (mask & s_mask) != 0) {
146         s_logger.print(s_msgFmt.formatMsg(sev, tag, format(fmt, args)));
147     }
148 }
149 
150 /// add a log entry with trace severity
151 void trace (in string tag, scope lazy string msg)
152 {
153     log (Severity.trace, tag, msg);
154 }
155 /// ditto
156 void trace (in uint mask, in string tag, scope lazy string msg)
157 {
158     log (Severity.trace, mask, tag, msg);
159 }
160 /// ditto
161 void tracef (Args...)(in string tag, in string fmt, Args args)
162 {
163     logf (Severity.trace, tag, fmt, args);
164 }
165 /// ditto
166 void tracef (Args...)(in uint mask, in string tag, in string fmt, Args args)
167 {
168     logf (Severity.trace, mask, tag, fmt, args);
169 }
170 
171 /// add a log entry with debug severity
172 void debug_ (in string tag, scope lazy string msg)
173 {
174     log (Severity.debug_, tag, msg);
175 }
176 /// ditto
177 void debug_ (in uint mask, in string tag, scope lazy string msg)
178 {
179     log (Severity.debug_, mask, tag, msg);
180 }
181 /// ditto
182 void debugf (Args...)(in string tag, in string fmt, Args args)
183 {
184     logf (Severity.debug_, tag, fmt, args);
185 }
186 /// ditto
187 void debugf (Args...)(in uint mask, in string tag, in string fmt, Args args)
188 {
189     logf (Severity.debug_, mask, tag, fmt, args);
190 }
191 
192 /// add a log entry with info severity
193 void info (in string tag, scope lazy string msg)
194 {
195     log (Severity.info, tag, msg);
196 }
197 /// ditto
198 void info (in uint mask, in string tag, scope lazy string msg)
199 {
200     log (Severity.info, mask, tag, msg);
201 }
202 /// ditto
203 void infof (Args...)(in string tag, in string fmt, Args args)
204 {
205     logf (Severity.info, tag, fmt, args);
206 }
207 /// ditto
208 void infof (Args...)(in uint mask, in string tag, in string fmt, Args args)
209 {
210     logf (Severity.info, mask, tag, fmt, args);
211 }
212 
213 /// add a log entry with warning severity
214 void warning (in string tag, scope lazy string msg)
215 {
216     log (Severity.warning, tag, msg);
217 }
218 /// ditto
219 void warning (in uint mask, in string tag, scope lazy string msg)
220 {
221     log (Severity.warning, mask, tag, msg);
222 }
223 /// ditto
224 void warningf (Args...)(in string tag, in string fmt, Args args)
225 {
226     logf (Severity.warning, tag, fmt, args);
227 }
228 /// ditto
229 void warningf (Args...)(in uint mask, in string tag, in string fmt, Args args)
230 {
231     logf (Severity.warning, mask, tag, fmt, args);
232 }
233 
234 /// add a log entry with error severity
235 void error (in string tag, scope lazy string msg)
236 {
237     log (Severity.error, tag, msg);
238 }
239 /// ditto
240 void error (in uint mask, in string tag, scope lazy string msg)
241 {
242     log (Severity.error, mask, tag, msg);
243 }
244 /// ditto
245 void errorf (Args...)(in string tag, in string fmt, Args args)
246 {
247     logf (Severity.error, tag, fmt, args);
248 }
249 /// ditto
250 void errorf (Args...)(in uint mask, in string tag, in string fmt, Args args)
251 {
252     logf (Severity.error, mask, tag, fmt, args);
253 }
254 
255 /// An abstract logger. All log operations are synchronized by a global mutex.
256 /// Implementations do not need to bother about thread safety.
257 interface Logger
258 {
259     /// print msg into the log
260     void print(in string msg);
261     /// release resource associated with the logger
262     void close();
263 }
264 
265 /// A logger that prints into a file
266 class FileLogger : Logger
267 {
268     import std.stdio : File;
269 
270     private File file;
271 
272     /// open file pointed to by name with mode
273     this (in string name, in string mode="a")
274     {
275         file = File(name, mode);
276     }
277 
278     override void print(in string msg)
279     {
280         file.writeln(msg);
281     }
282 
283     override void close()
284     {
285         file.close();
286     }
287 }
288 
289 /// A logger that prints to stdout
290 class StdOutLogger : Logger
291 {
292     override void print (in string msg)
293     {
294         import std.stdio : stdout;
295 
296         stdout.writeln(msg);
297     }
298 
299     override void close() {}
300 }
301 
302 /// A logger that prints to stderr
303 class StdErrLogger : Logger
304 {
305     override void print (in string msg)
306     {
307         import std.stdio : stderr;
308 
309         stderr.writeln(msg);
310     }
311 
312     override void close() {}
313 }
314 
315 /// The installed logger.
316 /// By default StdOutLogger.
317 /// Assigning null re-assign the default
318 @property Logger logger()
319 {
320     s_mutex.lock();
321     scope(exit) s_mutex.unlock();
322 
323     return s_logger;
324 }
325 /// ditto
326 @property void logger (Logger logger)
327 {
328     s_mutex.lock();
329     scope(exit) s_mutex.unlock();
330 
331     if (logger !is s_logger) {
332         s_logger.close();
333     }
334     s_logger = logger;
335     if (!s_logger) {
336         s_logger = new StdOutLogger;
337     }
338 }
339 
340 /// Minimum Severity for message filtering.
341 /// All messages with lower severity are filtered out.
342 /// by default Severity.info.
343 @property Severity severity()
344 {
345     s_mutex.lock();
346     scope(exit) s_mutex.unlock();
347 
348     return s_sev;
349 }
350 /// ditto
351 @property void severity (in Severity sev)
352 {
353     s_mutex.lock();
354     scope(exit) s_mutex.unlock();
355 
356     s_sev = sev;
357 }
358 
359 /// A mask for bypassing some messages.
360 /// Should only be used in debug builds.
361 /// by default 0xffffffff
362 @property uint mask()
363 {
364     s_mutex.lock();
365     scope(exit) s_mutex.unlock();
366 
367     return s_mask;
368 }
369 /// ditto
370 @property void mask(in uint mask)
371 {
372     s_mutex.lock();
373     scope(exit) s_mutex.unlock();
374 
375     s_mask = mask;
376 }
377 
378 /// the default format string for log messages
379 enum string defaultMsgFormat = "%d %s: %t %m";
380 
381 /// The format string for log messages
382 /// The entries are as follow:
383 ///  - %d: datetime (formatted according timeFormat)
384 ///  - %s: severity
385 ///  - %t: tag
386 ///  - %m: msg
387 /// The default format string is defaultMsgFormat
388 @property string msgFormat()
389 {
390     s_mutex.lock();
391     scope(exit) s_mutex.unlock();
392 
393     return s_msgFmt.fmt;
394 }
395 /// ditto
396 @property void msgFormat(in string fmt)
397 {
398     import std.utf : validate;
399 
400     validate(fmt);
401 
402     s_mutex.lock();
403     scope(exit) s_mutex.unlock();
404 
405     s_msgFmt = MessageFmt(fmt);
406 }
407 
408 /// the default format string for date-time
409 enum string defaultTimeFormat = "HH:ii:ss.FFF";
410 
411 /// The format string for date-time in log message
412 /// The format of this string is the same as smjg.libs.util.datetime:
413 /// http://pr.stewartsplace.org.uk/d/sutil/doc/datetimeformat.html
414 @property string timeFormat()
415 {
416     s_mutex.lock();
417     scope(exit) s_mutex.unlock();
418 
419     return s_timeFmt;
420 }
421 /// ditto
422 @property void timeFormat(in string fmt)
423 {
424     import std.utf : validate;
425 
426     validate(fmt);
427 
428     s_mutex.lock();
429     scope(exit) s_mutex.unlock();
430 
431     s_timeFmt = fmt;
432 }
433 
434 private:
435 
436 __gshared {
437     Mutex s_mutex;
438     Logger s_logger;
439     Severity s_sev;
440     uint s_mask;
441     string s_timeFmt;
442     MessageFmt s_msgFmt;
443 }
444 
445 shared static this()
446 {
447     s_mutex = new Mutex();
448     s_logger = new StdOutLogger;
449     s_sev = defaultSeverity;
450     s_mask = defaultMask;
451     s_timeFmt = defaultTimeFormat;
452     s_msgFmt = MessageFmt(defaultMsgFormat);
453 }
454 
455 shared static ~this()
456 {
457     s_logger.close();
458 }
459 
460 struct MessageFmt
461 {
462     enum EntryType {
463         str, datetime, sev, tag, msg,
464     }
465     struct Entry {
466         EntryType typ;
467         string str;
468     }
469 
470     this (string fmt)
471     {
472         this.fmt = fmt;
473 
474         string curStr;
475         bool percent;
476         foreach (char c; fmt) {
477             if (percent) {
478                 switch (c) {
479                 case 'd': entries ~= Entry(EntryType.datetime); break;
480                 case 's': entries ~= Entry(EntryType.sev); break;
481                 case 't': entries ~= Entry(EntryType.tag); break;
482                 case 'm': entries ~= Entry(EntryType.msg); break;
483                 default: throw new Exception("unknown log format entry: %"~c);
484                 }
485                 percent = false;
486             }
487             else if (c == '%') {
488                 if (curStr.length) {
489                     entries ~= Entry(EntryType.str, curStr);
490                     curStr = null;
491                 }
492                 percent = true;
493             }
494             else {
495                 curStr ~= c;
496             }
497         }
498         if (curStr.length) {
499             entries ~= Entry(EntryType.str, curStr);
500         }
501     }
502 
503     string fmt;
504     Entry[] entries;
505 
506     string formatMsg(in Severity sev, in string tag, in string msg)
507     {
508         import std.datetime : Clock;
509 
510         string res;
511         foreach (entry; entries) {
512             final switch (entry.typ) {
513             case EntryType.str:  res ~= entry.str;                      break;
514             case EntryType.datetime: res ~= formatTime(Clock.currTime); break;
515             case EntryType.sev: res ~= sevString(sev);                  break;
516             case EntryType.tag : res ~= tag;                            break;
517             case EntryType.msg : res ~= msg;                            break;
518             }
519         }
520         return res;
521     }
522 }
523 
524 string sevString(in Severity sev) pure
525 {
526     final switch (sev)
527     {
528     case Severity.trace: return "TRACE";
529     case Severity.debug_: return "DEBUG";
530     case Severity.info: return "INFO";
531     case Severity.warning: return "WARN";
532     case Severity.error: return "ERROR";
533     }
534 }
535 
536 string formatTime(SysTime time)
537 {
538     import gfx.priv.datetimeformat : format;
539 
540     return format(time, s_timeFmt);
541 }