1 // Written in the D programming language.
2 /**
3 Source: $(PHOBOSSRC std/experimental/logger/core.d)
4 */
5 module std.experimental.logger.core;
6 
7 import core.sync.mutex : Mutex;
8 import std.datetime.date : DateTime;
9 import std.datetime.systime : Clock, SysTime;
10 import std.range.primitives;
11 import std.traits;
12 
13 import std.experimental.logger.filelogger;
14 
15 /** This template evaluates if the passed `LogLevel` is active.
16 The previously described version statements are used to decide if the
17 `LogLevel` is active. The version statements only influence the compile
18 unit they are used with, therefore this function can only disable logging this
19 specific compile unit.
20 */
21 template isLoggingActiveAt(LogLevel ll)
22 {
23     version (StdLoggerDisableLogging)
24     {
25         enum isLoggingActiveAt = false;
26     }
27     else
28     {
29         static if (ll == LogLevel.trace)
30         {
31             version (StdLoggerDisableTrace) enum isLoggingActiveAt = false;
32         }
33         else static if (ll == LogLevel.info)
34         {
35             version (StdLoggerDisableInfo) enum isLoggingActiveAt = false;
36         }
37         else static if (ll == LogLevel.warning)
38         {
39             version (StdLoggerDisableWarning) enum isLoggingActiveAt = false;
40         }
41         else static if (ll == LogLevel.error)
42         {
43             version (StdLoggerDisableError) enum isLoggingActiveAt = false;
44         }
45         else static if (ll == LogLevel.critical)
46         {
47             version (StdLoggerDisableCritical) enum isLoggingActiveAt = false;
48         }
49         else static if (ll == LogLevel.fatal)
50         {
51             version (StdLoggerDisableFatal) enum isLoggingActiveAt = false;
52         }
53         // If `isLoggingActiveAt` didn't get defined above to false,
54         // we default it to true.
55         static if (!is(typeof(isLoggingActiveAt) == bool))
56         {
57             enum isLoggingActiveAt = true;
58         }
59     }
60 }
61 
62 /// This compile-time flag is `true` if logging is not statically disabled.
63 enum isLoggingActive = isLoggingActiveAt!(LogLevel.all);
64 
65 /** This functions is used at runtime to determine if a `LogLevel` is
66 active. The same previously defined version statements are used to disable
67 certain levels. Again the version statements are associated with a compile
68 unit and can therefore not disable logging in other compile units.
69 pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc
70 */
71 bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL,
72     LogLevel globalLL, lazy bool condition = true) @safe
73 {
74     switch (ll)
75     {
76         case LogLevel.trace:
77             version (StdLoggerDisableTrace) return false;
78             else break;
79         case LogLevel.info:
80             version (StdLoggerDisableInfo) return false;
81             else break;
82         case LogLevel.warning:
83             version (StdLoggerDisableWarning) return false;
84             else break;
85         case LogLevel.critical:
86             version (StdLoggerDisableCritical) return false;
87             else break;
88         case LogLevel.fatal:
89             version (StdLoggerDisableFatal) return false;
90             else break;
91         default: break;
92     }
93 
94     return ll >= globalLL
95         && ll >= loggerLL
96         && ll != LogLevel.off
97         && globalLL != LogLevel.off
98         && loggerLL != LogLevel.off
99         && condition;
100 }
101 
102 /** This template returns the `LogLevel` named "logLevel" of type $(D
103 LogLevel) defined in a user defined module where the filename has the
104 suffix "_loggerconfig.d". This `LogLevel` sets the minimal `LogLevel`
105 of the module.
106 
107 A minimal `LogLevel` can be defined on a per module basis.
108 In order to define a module `LogLevel` a file with a modulename
109 "MODULENAME_loggerconfig" must be found. If no such module exists and the
110 module is a nested module, it is checked if there exists a
111 "PARENT_MODULE_loggerconfig" module with such a symbol.
112 If this module exists and it contains a `LogLevel` called logLevel this $(D
113 LogLevel) will be used. This parent lookup is continued until there is no
114 parent module. Then the moduleLogLevel is `LogLevel.all`.
115 */
116 template moduleLogLevel(string moduleName)
117 if (!moduleName.length)
118 {
119     // default
120     enum moduleLogLevel = LogLevel.all;
121 }
122 
123 ///
124 @system unittest
125 {
126     static assert(moduleLogLevel!"" == LogLevel.all);
127 }
128 
129 /// ditto
130 template moduleLogLevel(string moduleName)
131 if (moduleName.length)
132 {
133     import std..string : format;
134     mixin(q{
135         static if (__traits(compiles, {import %1$s : logLevel;}))
136         {
137             import %1$s : logLevel;
138             static assert(is(typeof(logLevel) : LogLevel),
139                           "Expect 'logLevel' to be of Type 'LogLevel'.");
140             // don't enforce enum here
141             alias moduleLogLevel = logLevel;
142         }
143         else
144             // use logLevel of package or default
145             alias moduleLogLevel = moduleLogLevel!(parentOf(moduleName));
146     }.format(moduleName ~ "_loggerconfig"));
147 }
148 
149 ///
150 @system unittest
151 {
152     static assert(moduleLogLevel!"not.amodule.path" == LogLevel.all);
153 }
154 
155 private string parentOf(string mod)
156 {
157     foreach_reverse (i, c; mod)
158         if (c == '.') return mod[0 .. i];
159     return null;
160 }
161 
162 /* This function formates a `SysTime` into an `OutputRange`.
163 
164 The `SysTime` is formatted similar to
165 $(LREF std.datatime.DateTime.toISOExtString) except the fractional second part.
166 The fractional second part is in milliseconds and is always 3 digits.
167 */
168 void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time)
169 if (isOutputRange!(OutputRange,string))
170 {
171     import std.format : formattedWrite;
172 
173     const auto dt = cast(DateTime) time;
174     const auto fsec = time.fracSecs.total!"msecs";
175 
176     formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d",
177         dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
178         fsec);
179 }
180 
181 /** This function logs data.
182 
183 In order for the data to be processed, the `LogLevel` of the log call must
184 be greater or equal to the `LogLevel` of the `sharedLog` and the
185 `defaultLogLevel`; additionally the condition passed must be `true`.
186 
187 Params:
188   ll = The `LogLevel` used by this log call.
189   condition = The condition must be `true` for the data to be logged.
190   args = The data that should be logged.
191 
192 Example:
193 --------------------
194 log(LogLevel.warning, true, "Hello World", 3.1415);
195 --------------------
196 */
197 void log(int line = __LINE__, string file = __FILE__,
198     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
199     string moduleName = __MODULE__, A...)(const LogLevel ll,
200     lazy bool condition, lazy A args)
201 if (args.length != 1)
202 {
203     static if (isLoggingActive)
204     {
205         if (ll >= moduleLogLevel!moduleName)
206         {
207                stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName)
208                 (ll, condition, args);
209         }
210     }
211 }
212 
213 /// Ditto
214 void log(T, string moduleName = __MODULE__)(const LogLevel ll,
215     lazy bool condition, lazy T arg, int line = __LINE__, string file = __FILE__,
216     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__)
217 {
218     static if (isLoggingActive)
219     {
220         if (ll >= moduleLogLevel!moduleName)
221         {
222             stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName,
223                 prettyFuncName);
224         }
225     }
226 }
227 
228 /** This function logs data.
229 
230 In order for the data to be processed the `LogLevel` of the log call must
231 be greater or equal to the `LogLevel` of the `sharedLog`.
232 
233 Params:
234   ll = The `LogLevel` used by this log call.
235   args = The data that should be logged.
236 
237 Example:
238 --------------------
239 log(LogLevel.warning, "Hello World", 3.1415);
240 --------------------
241 */
242 void log(int line = __LINE__, string file = __FILE__,
243     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
244     string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args)
245 if (args.length > 1 && !is(Unqual!(A[0]) : bool))
246 {
247     static if (isLoggingActive)
248     {
249         if (ll >= moduleLogLevel!moduleName)
250         {
251             stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName)
252                 (ll, args);
253         }
254     }
255 }
256 
257 /// Ditto
258 void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg,
259     int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__,
260     string prettyFuncName = __PRETTY_FUNCTION__)
261 {
262     static if (isLoggingActive)
263     {
264         if (ll >= moduleLogLevel!moduleName)
265         {
266             stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName,
267                 moduleName);
268         }
269     }
270 }
271 
272 /** This function logs data.
273 
274 In order for the data to be processed the `LogLevel` of the
275 `sharedLog` must be greater or equal to the `defaultLogLevel`
276 add the condition passed must be `true`.
277 
278 Params:
279   condition = The condition must be `true` for the data to be logged.
280   args = The data that should be logged.
281 
282 Example:
283 --------------------
284 log(true, "Hello World", 3.1415);
285 --------------------
286 */
287 void log(int line = __LINE__, string file = __FILE__,
288     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
289     string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args)
290 if (args.length != 1)
291 {
292     static if (isLoggingActive)
293     {
294         stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName)
295             (stdThreadLocalLog.logLevel, condition, args);
296     }
297 }
298 
299 /// Ditto
300 void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg,
301     int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__,
302     string prettyFuncName = __PRETTY_FUNCTION__)
303 {
304     static if (isLoggingActive)
305     {
306         stdThreadLocalLog.log!(T,moduleName)(stdThreadLocalLog.logLevel,
307             condition, arg, line, file, funcName, prettyFuncName);
308     }
309 }
310 
311 /** This function logs data.
312 
313 In order for the data to be processed the `LogLevel` of the
314 `sharedLog` must be greater or equal to the `defaultLogLevel`.
315 
316 Params:
317   args = The data that should be logged.
318 
319 Example:
320 --------------------
321 log("Hello World", 3.1415);
322 --------------------
323 */
324 void log(int line = __LINE__, string file = __FILE__,
325     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
326     string moduleName = __MODULE__, A...)(lazy A args)
327 if ((args.length > 1 && !is(Unqual!(A[0]) : bool)
328     && !is(Unqual!(A[0]) == LogLevel))
329     || args.length == 0)
330 {
331     static if (isLoggingActive)
332     {
333         stdThreadLocalLog.log!(line, file, funcName,
334            prettyFuncName, moduleName)(stdThreadLocalLog.logLevel, args);
335     }
336 }
337 
338 void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__,
339     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
340     string moduleName = __MODULE__)
341 {
342     static if (isLoggingActive)
343     {
344         stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file,
345             funcName, prettyFuncName, moduleName);
346     }
347 }
348 
349 /** This function logs data in a `printf`-style manner.
350 
351 In order for the data to be processed the `LogLevel` of the log call must
352 be greater or equal to the `LogLevel` of the `sharedLog` and the
353 `defaultLogLevel` additionally the condition passed must be `true`.
354 
355 Params:
356   ll = The `LogLevel` used by this log call.
357   condition = The condition must be `true` for the data to be logged.
358   msg = The `printf`-style string.
359   args = The data that should be logged.
360 
361 Example:
362 --------------------
363 logf(LogLevel.warning, true, "Hello World %f", 3.1415);
364 --------------------
365 */
366 void logf(int line = __LINE__, string file = __FILE__,
367     string funcName = __FUNCTION__,
368     string prettyFuncName = __PRETTY_FUNCTION__,
369     string moduleName = __MODULE__, A...)(const LogLevel ll,
370     lazy bool condition, lazy string msg, lazy A args)
371 {
372     static if (isLoggingActive)
373     {
374         if (ll >= moduleLogLevel!moduleName)
375         {
376             stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
377                 (ll, condition, msg, args);
378         }
379     }
380 }
381 
382 /** This function logs data in a `printf`-style manner.
383 
384 In order for the data to be processed the `LogLevel` of the log call must
385 be greater or equal to the `LogLevel` of the `sharedLog` and the
386 `defaultLogLevel`.
387 
388 Params:
389   ll = The `LogLevel` used by this log call.
390   msg = The `printf`-style string.
391   args = The data that should be logged.
392 
393 Example:
394 --------------------
395 logf(LogLevel.warning, "Hello World %f", 3.1415);
396 --------------------
397 */
398 void logf(int line = __LINE__, string file = __FILE__,
399     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
400     string moduleName = __MODULE__, A...)(const LogLevel ll, lazy string msg,
401         lazy A args)
402 {
403     static if (isLoggingActive)
404     {
405         if (ll >= moduleLogLevel!moduleName)
406         {
407             stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
408                 (ll, msg, args);
409         }
410     }
411 }
412 
413 /** This function logs data in a `printf`-style manner.
414 
415 In order for the data to be processed the `LogLevel` of the log call must
416 be greater or equal to the `defaultLogLevel` additionally the condition
417 passed must be `true`.
418 
419 Params:
420   condition = The condition must be `true` for the data to be logged.
421   msg = The `printf`-style string.
422   args = The data that should be logged.
423 
424 Example:
425 --------------------
426 logf(true, "Hello World %f", 3.1415);
427 --------------------
428 */
429 void logf(int line = __LINE__, string file = __FILE__,
430     string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__,
431     string moduleName = __MODULE__, A...)(lazy bool condition,
432         lazy string msg, lazy A args)
433 {
434     static if (isLoggingActive)
435     {
436         stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
437             (stdThreadLocalLog.logLevel, condition, msg, args);
438     }
439 }
440 
441 /** This function logs data in a `printf`-style manner.
442 
443 In order for the data to be processed the `LogLevel` of the log call must
444 be greater or equal to the `defaultLogLevel`.
445 
446 Params:
447   msg = The `printf`-style string.
448   args = The data that should be logged.
449 
450 Example:
451 --------------------
452 logf("Hello World %f", 3.1415);
453 --------------------
454 */
455 void logf(int line = __LINE__, string file = __FILE__,
456     string funcName = __FUNCTION__,
457     string prettyFuncName = __PRETTY_FUNCTION__,
458     string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
459 {
460     static if (isLoggingActive)
461     {
462         stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName)
463             (stdThreadLocalLog.logLevel, msg, args);
464     }
465 }
466 
467 /** This template provides the global log functions with the `LogLevel`
468 is encoded in the function name.
469 
470 The aliases following this template create the public names of these log
471 functions.
472 */
473 template defaultLogFunction(LogLevel ll)
474 {
475     void defaultLogFunction(int line = __LINE__, string file = __FILE__,
476         string funcName = __FUNCTION__,
477         string prettyFuncName = __PRETTY_FUNCTION__,
478         string moduleName = __MODULE__, A...)(lazy A args)
479         if ((args.length > 0 && !is(Unqual!(A[0]) : bool)) || args.length == 0)
480     {
481         static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
482         {
483             stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName,
484                 prettyFuncName, moduleName)(args);
485         }
486     }
487 
488     void defaultLogFunction(int line = __LINE__, string file = __FILE__,
489         string funcName = __FUNCTION__,
490         string prettyFuncName = __PRETTY_FUNCTION__,
491         string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args)
492     {
493         static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
494         {
495             stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName,
496                 prettyFuncName, moduleName)(condition, args);
497         }
498     }
499 }
500 
501 /** This function logs data to the `stdThreadLocalLog`, optionally depending
502 on a condition.
503 
504 In order for the resulting log message to be logged the `LogLevel` must
505 be greater or equal than the `LogLevel` of the `stdThreadLocalLog` and
506 must be greater or equal than the global `LogLevel`.
507 Additionally the `LogLevel` must be greater or equal than the `LogLevel`
508 of the `stdSharedLogger`.
509 If a condition is given, it must evaluate to `true`.
510 
511 Params:
512   condition = The condition must be `true` for the data to be logged.
513   args = The data that should be logged.
514 
515 Example:
516 --------------------
517 trace(1337, "is number");
518 info(1337, "is number");
519 error(1337, "is number");
520 critical(1337, "is number");
521 fatal(1337, "is number");
522 trace(true, 1337, "is number");
523 info(false, 1337, "is number");
524 error(true, 1337, "is number");
525 critical(false, 1337, "is number");
526 fatal(true, 1337, "is number");
527 --------------------
528 */
529 alias trace = defaultLogFunction!(LogLevel.trace);
530 /// Ditto
531 alias info = defaultLogFunction!(LogLevel.info);
532 /// Ditto
533 alias warning = defaultLogFunction!(LogLevel.warning);
534 /// Ditto
535 alias error = defaultLogFunction!(LogLevel.error);
536 /// Ditto
537 alias critical = defaultLogFunction!(LogLevel.critical);
538 /// Ditto
539 alias fatal = defaultLogFunction!(LogLevel.fatal);
540 
541 /** This template provides the global `printf`-style log functions with
542 the `LogLevel` is encoded in the function name.
543 
544 The aliases following this template create the public names of the log
545 functions.
546 */
547 template defaultLogFunctionf(LogLevel ll)
548 {
549     void defaultLogFunctionf(int line = __LINE__, string file = __FILE__,
550         string funcName = __FUNCTION__,
551         string prettyFuncName = __PRETTY_FUNCTION__,
552         string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
553     {
554         static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
555         {
556             stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName,
557                 prettyFuncName, moduleName)(msg, args);
558         }
559     }
560 
561     void defaultLogFunctionf(int line = __LINE__, string file = __FILE__,
562         string funcName = __FUNCTION__,
563         string prettyFuncName = __PRETTY_FUNCTION__,
564         string moduleName = __MODULE__, A...)(lazy bool condition,
565             lazy string msg, lazy A args)
566     {
567         static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
568         {
569             stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName,
570                 prettyFuncName, moduleName)(condition, msg, args);
571         }
572     }
573 }
574 
575 /** This function logs data to the `sharedLog` in a `printf`-style
576 manner.
577 
578 In order for the resulting log message to be logged the `LogLevel` must
579 be greater or equal than the `LogLevel` of the `sharedLog` and
580 must be greater or equal than the global `LogLevel`.
581 Additionally the `LogLevel` must be greater or equal than the `LogLevel`
582 of the `stdSharedLogger`.
583 
584 Params:
585   msg = The `printf`-style string.
586   args = The data that should be logged.
587 
588 Example:
589 --------------------
590 tracef("is number %d", 1);
591 infof("is number %d", 2);
592 errorf("is number %d", 3);
593 criticalf("is number %d", 4);
594 fatalf("is number %d", 5);
595 --------------------
596 
597 The second version of the function logs data to the `sharedLog` in a $(D
598 printf)-style manner.
599 
600 In order for the resulting log message to be logged the `LogLevel` must
601 be greater or equal than the `LogLevel` of the `sharedLog` and
602 must be greater or equal than the global `LogLevel`.
603 Additionally the `LogLevel` must be greater or equal than the `LogLevel`
604 of the `stdSharedLogger`.
605 
606 Params:
607   condition = The condition must be `true` for the data to be logged.
608   msg = The `printf`-style string.
609   args = The data that should be logged.
610 
611 Example:
612 --------------------
613 tracef(false, "is number %d", 1);
614 infof(false, "is number %d", 2);
615 errorf(true, "is number %d", 3);
616 criticalf(true, "is number %d", 4);
617 fatalf(someFunct(), "is number %d", 5);
618 --------------------
619 */
620 alias tracef = defaultLogFunctionf!(LogLevel.trace);
621 /// Ditto
622 alias infof = defaultLogFunctionf!(LogLevel.info);
623 /// Ditto
624 alias warningf = defaultLogFunctionf!(LogLevel.warning);
625 /// Ditto
626 alias errorf = defaultLogFunctionf!(LogLevel.error);
627 /// Ditto
628 alias criticalf = defaultLogFunctionf!(LogLevel.critical);
629 /// Ditto
630 alias fatalf = defaultLogFunctionf!(LogLevel.fatal);
631 
632 private struct MsgRange
633 {
634     import std.traits : isSomeString, isSomeChar;
635 
636     private Logger log;
637 
638     this(Logger log) @safe
639     {
640         this.log = log;
641     }
642 
643     void put(T)(T msg) @safe
644         if (isSomeString!T)
645     {
646         log.logMsgPart(msg);
647     }
648 
649     void put(dchar elem) @safe
650     {
651         import std.utf : encode;
652         char[4] buffer;
653         size_t len = encode(buffer, elem);
654         log.logMsgPart(buffer[0 .. len]);
655     }
656 }
657 
658 private void formatString(A...)(MsgRange oRange, A args)
659 {
660     import std.format : formattedWrite;
661 
662     foreach (arg; args)
663     {
664         formattedWrite(oRange, "%s", arg);
665     }
666 }
667 
668 @system unittest
669 {
670     void dummy() @safe
671     {
672         auto tl = new TestLogger();
673         auto dst = MsgRange(tl);
674         formatString(dst, "aaa", "bbb");
675     }
676 
677     dummy();
678 }
679 
680 /**
681 There are eight usable logging level. These level are $(I all), $(I trace),
682 $(I info), $(I warning), $(I error), $(I critical), $(I fatal), and $(I off).
683 If a log function with `LogLevel.fatal` is called the shutdown handler of
684 that logger is called.
685 */
686 enum LogLevel : ubyte
687 {
688     all = 1, /** Lowest possible assignable `LogLevel`. */
689     trace = 32, /** `LogLevel` for tracing the execution of the program. */
690     info = 64, /** This level is used to display information about the
691                 program. */
692     warning = 96, /** warnings about the program should be displayed with this
693                    level. */
694     error = 128, /** Information about errors should be logged with this
695                    level.*/
696     critical = 160, /** Messages that inform about critical errors should be
697                     logged with this level. */
698     fatal = 192,   /** Log messages that describe fatal errors should use this
699                   level. */
700     off = ubyte.max /** Highest possible `LogLevel`. */
701 }
702 
703 /** This class is the base of every logger. In order to create a new kind of
704 logger a deriving class needs to implement the `writeLogMsg` method. By
705 default this is not thread-safe.
706 
707 It is also possible to `override` the three methods `beginLogMsg`,
708 `logMsgPart` and `finishLogMsg` together, this option gives more
709 flexibility.
710 */
711 abstract class Logger
712 {
713     import std.array : appender, Appender;
714     import std.concurrency : thisTid, Tid;
715 
716     /** LogEntry is a aggregation combining all information associated
717     with a log message. This aggregation will be passed to the method
718     writeLogMsg.
719     */
720     protected struct LogEntry
721     {
722         /// the filename the log function was called from
723         string file;
724         /// the line number the log function was called from
725         int line;
726         /// the name of the function the log function was called from
727         string funcName;
728         /// the pretty formatted name of the function the log function was
729         /// called from
730         string prettyFuncName;
731         /// the name of the module the log message is coming from
732         string moduleName;
733         /// the `LogLevel` associated with the log message
734         LogLevel logLevel;
735         /// thread id of the log message
736         Tid threadId;
737         /// the time the message was logged
738         SysTime timestamp;
739         /// the message of the log message
740         string msg;
741         /// A refernce to the `Logger` used to create this `LogEntry`
742         Logger logger;
743     }
744 
745     /**
746     Every subclass of `Logger` has to call this constructor from their
747     constructor. It sets the `LogLevel`, and creates a fatal handler. The fatal
748     handler will throw an `Error` if a log call is made with level
749     `LogLevel.fatal`.
750 
751     Params:
752          lv = `LogLevel` to use for this `Logger` instance.
753     */
754     this(LogLevel lv) @safe
755     {
756         this.logLevel_ = lv;
757         this.fatalHandler_ = delegate() {
758             throw new Error("A fatal log message was logged");
759         };
760 
761         this.mutex = new Mutex();
762     }
763 
764     /** A custom logger must implement this method in order to work in a
765     `MultiLogger` and `ArrayLogger`.
766 
767     Params:
768       payload = All information associated with call to log function.
769 
770     See_Also: beginLogMsg, logMsgPart, finishLogMsg
771     */
772     abstract protected void writeLogMsg(ref LogEntry payload) @safe;
773 
774     /* The default implementation will use an `std.array.appender`
775     internally to construct the message string. This means dynamic,
776     GC memory allocation. A logger can avoid this allocation by
777     reimplementing `beginLogMsg`, `logMsgPart` and `finishLogMsg`.
778     `beginLogMsg` is always called first, followed by any number of calls
779     to `logMsgPart` and one call to `finishLogMsg`.
780 
781     As an example for such a custom `Logger` compare this:
782     ----------------
783     class CLogger : Logger
784     {
785         override void beginLogMsg(string file, int line, string funcName,
786             string prettyFuncName, string moduleName, LogLevel logLevel,
787             Tid threadId, SysTime timestamp)
788         {
789             ... logic here
790         }
791 
792         override void logMsgPart(const(char)[] msg)
793         {
794             ... logic here
795         }
796 
797         override void finishLogMsg()
798         {
799             ... logic here
800         }
801 
802         void writeLogMsg(ref LogEntry payload)
803         {
804             this.beginLogMsg(payload.file, payload.line, payload.funcName,
805                 payload.prettyFuncName, payload.moduleName, payload.logLevel,
806                 payload.threadId, payload.timestamp, payload.logger);
807 
808             this.logMsgPart(payload.msg);
809             this.finishLogMsg();
810         }
811     }
812     ----------------
813     */
814     protected void beginLogMsg(string file, int line, string funcName,
815         string prettyFuncName, string moduleName, LogLevel logLevel,
816         Tid threadId, SysTime timestamp, Logger logger)
817         @safe
818     {
819         static if (isLoggingActive)
820         {
821             msgAppender = appender!string();
822             header = LogEntry(file, line, funcName, prettyFuncName,
823                 moduleName, logLevel, threadId, timestamp, null, logger);
824         }
825     }
826 
827     /** Logs a part of the log message. */
828     protected void logMsgPart(scope const(char)[] msg) @safe
829     {
830         static if (isLoggingActive)
831         {
832                msgAppender.put(msg);
833         }
834     }
835 
836     /** Signals that the message has been written and no more calls to
837     `logMsgPart` follow. */
838     protected void finishLogMsg() @safe
839     {
840         static if (isLoggingActive)
841         {
842             header.msg = msgAppender.data;
843             this.writeLogMsg(header);
844         }
845     }
846 
847     /** The `LogLevel` determines if the log call are processed or dropped
848     by the `Logger`. In order for the log call to be processed the
849     `LogLevel` of the log call must be greater or equal to the `LogLevel`
850     of the `logger`.
851 
852     These two methods set and get the `LogLevel` of the used `Logger`.
853 
854     Example:
855     -----------
856     auto f = new FileLogger(stdout);
857     f.logLevel = LogLevel.info;
858     assert(f.logLevel == LogLevel.info);
859     -----------
860     */
861     @property final LogLevel logLevel() const pure @safe @nogc
862     {
863         return trustedLoad(this.logLevel_);
864     }
865 
866     /// Ditto
867     @property final void logLevel(const LogLevel lv) @safe @nogc
868     {
869         synchronized (mutex) this.logLevel_ = lv;
870     }
871 
872     /** This `delegate` is called in case a log message with
873     `LogLevel.fatal` gets logged.
874 
875     By default an `Error` will be thrown.
876     */
877     @property final void delegate() fatalHandler() @safe @nogc
878     {
879         synchronized (mutex) return this.fatalHandler_;
880     }
881 
882     /// Ditto
883     @property final void fatalHandler(void delegate() @safe fh) @safe @nogc
884     {
885         synchronized (mutex) this.fatalHandler_ = fh;
886     }
887 
888     /** This method allows forwarding log entries from one logger to another.
889 
890     `forwardMsg` will ensure proper synchronization and then call
891     `writeLogMsg`. This is an API for implementing your own loggers and
892     should not be called by normal user code. A notable difference from other
893     logging functions is that the `globalLogLevel` wont be evaluated again
894     since it is assumed that the caller already checked that.
895     */
896     void forwardMsg(ref LogEntry payload) @trusted
897     {
898         static if (isLoggingActive) synchronized (mutex)
899         {
900             if (isLoggingEnabled(payload.logLevel, this.logLevel_,
901                 globalLogLevel))
902             {
903                 this.writeLogMsg(payload);
904 
905                 if (payload.logLevel == LogLevel.fatal)
906                     this.fatalHandler_();
907             }
908         }
909     }
910 
911     /** This template provides the log functions for the `Logger` `class`
912     with the `LogLevel` encoded in the function name.
913 
914     For further information see the the two functions defined inside of this
915     template.
916 
917     The aliases following this template create the public names of these log
918     functions.
919     */
920     template memLogFunctions(LogLevel ll)
921     {
922         /** This function logs data to the used `Logger`.
923 
924         In order for the resulting log message to be logged the `LogLevel`
925         must be greater or equal than the `LogLevel` of the used `Logger`
926         and must be greater or equal than the global `LogLevel`.
927 
928         Params:
929           args = The data that should be logged.
930 
931         Example:
932         --------------------
933         auto s = new FileLogger(stdout);
934         s.trace(1337, "is number");
935         s.info(1337, "is number");
936         s.error(1337, "is number");
937         s.critical(1337, "is number");
938         s.fatal(1337, "is number");
939         --------------------
940         */
941         void logImpl(int line = __LINE__, string file = __FILE__,
942             string funcName = __FUNCTION__,
943             string prettyFuncName = __PRETTY_FUNCTION__,
944             string moduleName = __MODULE__, A...)(lazy A args)
945             if (args.length == 0 || (args.length > 0 && !is(A[0] : bool)))
946         {
947             static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
948                 synchronized (mutex)
949             {
950                 if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
951                 {
952                     this.beginLogMsg(file, line, funcName, prettyFuncName,
953                         moduleName, ll, thisTid, Clock.currTime, this);
954 
955                     auto writer = MsgRange(this);
956                     formatString(writer, args);
957 
958                     this.finishLogMsg();
959 
960                     static if (ll == LogLevel.fatal)
961                         this.fatalHandler_();
962                 }
963             }
964         }
965 
966         /** This function logs data to the used `Logger` depending on a
967         condition.
968 
969         In order for the resulting log message to be logged the `LogLevel` must
970         be greater or equal than the `LogLevel` of the used `Logger` and
971         must be greater or equal than the global `LogLevel` additionally the
972         condition passed must be `true`.
973 
974         Params:
975           condition = The condition must be `true` for the data to be logged.
976           args = The data that should be logged.
977 
978         Example:
979         --------------------
980         auto s = new FileLogger(stdout);
981         s.trace(true, 1337, "is number");
982         s.info(false, 1337, "is number");
983         s.error(true, 1337, "is number");
984         s.critical(false, 1337, "is number");
985         s.fatal(true, 1337, "is number");
986         --------------------
987         */
988         void logImpl(int line = __LINE__, string file = __FILE__,
989             string funcName = __FUNCTION__,
990             string prettyFuncName = __PRETTY_FUNCTION__,
991             string moduleName = __MODULE__, A...)(lazy bool condition,
992                 lazy A args)
993         {
994             static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
995                 synchronized (mutex)
996             {
997                 if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel,
998                                      condition))
999                 {
1000                     this.beginLogMsg(file, line, funcName, prettyFuncName,
1001                         moduleName, ll, thisTid, Clock.currTime, this);
1002 
1003                     auto writer = MsgRange(this);
1004                     formatString(writer, args);
1005 
1006                     this.finishLogMsg();
1007 
1008                     static if (ll == LogLevel.fatal)
1009                         this.fatalHandler_();
1010                 }
1011             }
1012         }
1013 
1014         /** This function logs data to the used `Logger` in a
1015         `printf`-style manner.
1016 
1017         In order for the resulting log message to be logged the `LogLevel`
1018         must be greater or equal than the `LogLevel` of the used `Logger`
1019         and must be greater or equal than the global `LogLevel` additionally
1020            the passed condition must be `true`.
1021 
1022         Params:
1023           condition = The condition must be `true` for the data to be logged.
1024           msg = The `printf`-style string.
1025           args = The data that should be logged.
1026 
1027         Example:
1028         --------------------
1029         auto s = new FileLogger(stderr);
1030         s.tracef(true, "is number %d", 1);
1031         s.infof(true, "is number %d", 2);
1032         s.errorf(false, "is number %d", 3);
1033         s.criticalf(someFunc(), "is number %d", 4);
1034         s.fatalf(true, "is number %d", 5);
1035         --------------------
1036         */
1037         void logImplf(int line = __LINE__, string file = __FILE__,
1038             string funcName = __FUNCTION__,
1039             string prettyFuncName = __PRETTY_FUNCTION__,
1040             string moduleName = __MODULE__, A...)(lazy bool condition,
1041                 lazy string msg, lazy A args)
1042         {
1043             static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
1044                 synchronized (mutex)
1045             {
1046                 import std.format : formattedWrite;
1047 
1048                 if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel,
1049                                      condition))
1050                 {
1051                     this.beginLogMsg(file, line, funcName, prettyFuncName,
1052                         moduleName, ll, thisTid, Clock.currTime, this);
1053 
1054                     auto writer = MsgRange(this);
1055                     formattedWrite(writer, msg, args);
1056 
1057                     this.finishLogMsg();
1058 
1059                     static if (ll == LogLevel.fatal)
1060                         this.fatalHandler_();
1061                 }
1062             }
1063         }
1064 
1065         /** This function logs data to the used `Logger` in a
1066         `printf`-style manner.
1067 
1068         In order for the resulting log message to be logged the `LogLevel` must
1069         be greater or equal than the `LogLevel` of the used `Logger` and
1070         must be greater or equal than the global `LogLevel`.
1071 
1072         Params:
1073           msg = The `printf`-style string.
1074           args = The data that should be logged.
1075 
1076         Example:
1077         --------------------
1078         auto s = new FileLogger(stderr);
1079         s.tracef("is number %d", 1);
1080         s.infof("is number %d", 2);
1081         s.errorf("is number %d", 3);
1082         s.criticalf("is number %d", 4);
1083         s.fatalf("is number %d", 5);
1084         --------------------
1085         */
1086         void logImplf(int line = __LINE__, string file = __FILE__,
1087             string funcName = __FUNCTION__,
1088             string prettyFuncName = __PRETTY_FUNCTION__,
1089             string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
1090         {
1091             static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName)
1092                 synchronized (mutex)
1093             {
1094                 import std.format : formattedWrite;
1095 
1096                 if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
1097                 {
1098                     this.beginLogMsg(file, line, funcName, prettyFuncName,
1099                         moduleName, ll, thisTid, Clock.currTime, this);
1100 
1101                     auto writer = MsgRange(this);
1102                     formattedWrite(writer, msg, args);
1103 
1104                     this.finishLogMsg();
1105 
1106                     static if (ll == LogLevel.fatal)
1107                         this.fatalHandler_();
1108                 }
1109             }
1110         }
1111     }
1112 
1113     /// Ditto
1114     alias trace = memLogFunctions!(LogLevel.trace).logImpl;
1115     /// Ditto
1116     alias tracef = memLogFunctions!(LogLevel.trace).logImplf;
1117     /// Ditto
1118     alias info = memLogFunctions!(LogLevel.info).logImpl;
1119     /// Ditto
1120     alias infof = memLogFunctions!(LogLevel.info).logImplf;
1121     /// Ditto
1122     alias warning = memLogFunctions!(LogLevel.warning).logImpl;
1123     /// Ditto
1124     alias warningf = memLogFunctions!(LogLevel.warning).logImplf;
1125     /// Ditto
1126     alias error = memLogFunctions!(LogLevel.error).logImpl;
1127     /// Ditto
1128     alias errorf = memLogFunctions!(LogLevel.error).logImplf;
1129     /// Ditto
1130     alias critical = memLogFunctions!(LogLevel.critical).logImpl;
1131     /// Ditto
1132     alias criticalf = memLogFunctions!(LogLevel.critical).logImplf;
1133     /// Ditto
1134     alias fatal = memLogFunctions!(LogLevel.fatal).logImpl;
1135     /// Ditto
1136     alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf;
1137 
1138     /** This method logs data with the `LogLevel` of the used `Logger`.
1139 
1140     This method takes a `bool` as first argument. In order for the
1141     data to be processed the `bool` must be `true` and the `LogLevel`
1142     of the Logger must be greater or equal to the global `LogLevel`.
1143 
1144     Params:
1145       args = The data that should be logged.
1146       condition = The condition must be `true` for the data to be logged.
1147       args = The data that is to be logged.
1148 
1149     Returns: The logger used by the logging function as reference.
1150 
1151     Example:
1152     --------------------
1153     auto l = new StdioLogger();
1154     l.log(1337);
1155     --------------------
1156     */
1157     void log(int line = __LINE__, string file = __FILE__,
1158         string funcName = __FUNCTION__,
1159         string prettyFuncName = __PRETTY_FUNCTION__,
1160         string moduleName = __MODULE__, A...)(const LogLevel ll,
1161         lazy bool condition, lazy A args)
1162         if (args.length != 1)
1163     {
1164         static if (isLoggingActive) synchronized (mutex)
1165         {
1166             if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition))
1167             {
1168                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1169                     moduleName, ll, thisTid, Clock.currTime, this);
1170 
1171                 auto writer = MsgRange(this);
1172                 formatString(writer, args);
1173 
1174                 this.finishLogMsg();
1175 
1176                 if (ll == LogLevel.fatal)
1177                     this.fatalHandler_();
1178             }
1179         }
1180     }
1181 
1182     /// Ditto
1183     void log(T, string moduleName = __MODULE__)(const LogLevel ll,
1184         lazy bool condition, lazy T args, int line = __LINE__,
1185         string file = __FILE__, string funcName = __FUNCTION__,
1186         string prettyFuncName = __PRETTY_FUNCTION__)
1187     {
1188         static if (isLoggingActive) synchronized (mutex)
1189         {
1190             if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel,
1191                 condition) && ll >= moduleLogLevel!moduleName)
1192             {
1193                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1194                     moduleName, ll, thisTid, Clock.currTime, this);
1195                 auto writer = MsgRange(this);
1196                 formatString(writer, args);
1197 
1198                 this.finishLogMsg();
1199 
1200                 if (ll == LogLevel.fatal)
1201                     this.fatalHandler_();
1202             }
1203         }
1204     }
1205 
1206     /** This function logs data to the used `Logger` with a specific
1207     `LogLevel`.
1208 
1209     In order for the resulting log message to be logged the `LogLevel`
1210     must be greater or equal than the `LogLevel` of the used `Logger`
1211     and must be greater or equal than the global `LogLevel`.
1212 
1213     Params:
1214       ll = The specific `LogLevel` used for logging the log message.
1215       args = The data that should be logged.
1216 
1217     Example:
1218     --------------------
1219     auto s = new FileLogger(stdout);
1220     s.log(LogLevel.trace, 1337, "is number");
1221     s.log(LogLevel.info, 1337, "is number");
1222     s.log(LogLevel.warning, 1337, "is number");
1223     s.log(LogLevel.error, 1337, "is number");
1224     s.log(LogLevel.fatal, 1337, "is number");
1225     --------------------
1226     */
1227     void log(int line = __LINE__, string file = __FILE__,
1228         string funcName = __FUNCTION__,
1229         string prettyFuncName = __PRETTY_FUNCTION__,
1230         string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args)
1231         if ((args.length > 1 && !is(Unqual!(A[0]) : bool)) || args.length == 0)
1232     {
1233         static if (isLoggingActive) synchronized (mutex)
1234         {
1235             if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
1236             {
1237                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1238                     moduleName, ll, thisTid, Clock.currTime, this);
1239 
1240                 auto writer = MsgRange(this);
1241                 formatString(writer, args);
1242 
1243                 this.finishLogMsg();
1244 
1245                 if (ll == LogLevel.fatal)
1246                     this.fatalHandler_();
1247             }
1248         }
1249     }
1250 
1251     /// Ditto
1252     void log(T)(const LogLevel ll, lazy T args, int line = __LINE__,
1253         string file = __FILE__, string funcName = __FUNCTION__,
1254         string prettyFuncName = __PRETTY_FUNCTION__,
1255         string moduleName = __MODULE__)
1256     {
1257         static if (isLoggingActive) synchronized (mutex)
1258         {
1259             if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
1260             {
1261                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1262                     moduleName, ll, thisTid, Clock.currTime, this);
1263                 auto writer = MsgRange(this);
1264                 formatString(writer, args);
1265 
1266                 this.finishLogMsg();
1267 
1268                 if (ll == LogLevel.fatal)
1269                     this.fatalHandler_();
1270             }
1271         }
1272     }
1273 
1274     /** This function logs data to the used `Logger` depending on a
1275     explicitly passed condition with the `LogLevel` of the used
1276     `Logger`.
1277 
1278     In order for the resulting log message to be logged the `LogLevel`
1279     of the used `Logger` must be greater or equal than the global
1280     `LogLevel` and the condition must be `true`.
1281 
1282     Params:
1283       condition = The condition must be `true` for the data to be logged.
1284       args = The data that should be logged.
1285 
1286     Example:
1287     --------------------
1288     auto s = new FileLogger(stdout);
1289     s.log(true, 1337, "is number");
1290     s.log(true, 1337, "is number");
1291     s.log(true, 1337, "is number");
1292     s.log(false, 1337, "is number");
1293     s.log(false, 1337, "is number");
1294     --------------------
1295     */
1296     void log(int line = __LINE__, string file = __FILE__,
1297         string funcName = __FUNCTION__,
1298         string prettyFuncName = __PRETTY_FUNCTION__,
1299         string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args)
1300         if (args.length != 1)
1301     {
1302         static if (isLoggingActive) synchronized (mutex)
1303         {
1304             if (isLoggingEnabled(this.logLevel_, this.logLevel_,
1305                 globalLogLevel, condition))
1306             {
1307                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1308                     moduleName, this.logLevel_, thisTid, Clock.currTime, this);
1309 
1310                 auto writer = MsgRange(this);
1311                 formatString(writer, args);
1312 
1313                 this.finishLogMsg();
1314 
1315                 if (this.logLevel_ == LogLevel.fatal)
1316                     this.fatalHandler_();
1317             }
1318         }
1319     }
1320 
1321     /// Ditto
1322     void log(T)(lazy bool condition, lazy T args, int line = __LINE__,
1323         string file = __FILE__, string funcName = __FUNCTION__,
1324         string prettyFuncName = __PRETTY_FUNCTION__,
1325         string moduleName = __MODULE__)
1326     {
1327         static if (isLoggingActive) synchronized (mutex)
1328         {
1329             if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel,
1330                 condition))
1331             {
1332                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1333                     moduleName, this.logLevel_, thisTid, Clock.currTime, this);
1334                 auto writer = MsgRange(this);
1335                 formatString(writer, args);
1336 
1337                 this.finishLogMsg();
1338 
1339                 if (this.logLevel_ == LogLevel.fatal)
1340                     this.fatalHandler_();
1341             }
1342         }
1343     }
1344 
1345     /** This function logs data to the used `Logger` with the `LogLevel`
1346     of the used `Logger`.
1347 
1348     In order for the resulting log message to be logged the `LogLevel`
1349     of the used `Logger` must be greater or equal than the global
1350     `LogLevel`.
1351 
1352     Params:
1353       args = The data that should be logged.
1354 
1355     Example:
1356     --------------------
1357     auto s = new FileLogger(stdout);
1358     s.log(1337, "is number");
1359     s.log(info, 1337, "is number");
1360     s.log(1337, "is number");
1361     s.log(1337, "is number");
1362     s.log(1337, "is number");
1363     --------------------
1364     */
1365     void log(int line = __LINE__, string file = __FILE__,
1366         string funcName = __FUNCTION__,
1367         string prettyFuncName = __PRETTY_FUNCTION__,
1368         string moduleName = __MODULE__, A...)(lazy A args)
1369         if ((args.length > 1
1370                 && !is(Unqual!(A[0]) : bool)
1371                 && !is(immutable A[0] == immutable LogLevel))
1372             || args.length == 0)
1373     {
1374         static if (isLoggingActive) synchronized (mutex)
1375         {
1376             if (isLoggingEnabled(this.logLevel_, this.logLevel_,
1377                 globalLogLevel))
1378             {
1379                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1380                     moduleName, this.logLevel_, thisTid, Clock.currTime, this);
1381                 auto writer = MsgRange(this);
1382                 formatString(writer, args);
1383 
1384                 this.finishLogMsg();
1385 
1386                 if (this.logLevel_ == LogLevel.fatal)
1387                     this.fatalHandler_();
1388             }
1389         }
1390     }
1391 
1392     /// Ditto
1393     void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__,
1394         string funcName = __FUNCTION__,
1395         string prettyFuncName = __PRETTY_FUNCTION__,
1396         string moduleName = __MODULE__)
1397     {
1398         static if (isLoggingActive) synchronized (mutex)
1399         {
1400             if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel))
1401             {
1402                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1403                     moduleName, this.logLevel_, thisTid, Clock.currTime, this);
1404                 auto writer = MsgRange(this);
1405                 formatString(writer, arg);
1406 
1407                 this.finishLogMsg();
1408 
1409                 if (this.logLevel_ == LogLevel.fatal)
1410                     this.fatalHandler_();
1411             }
1412         }
1413     }
1414 
1415     /** This function logs data to the used `Logger` with a specific
1416     `LogLevel` and depending on a condition in a `printf`-style manner.
1417 
1418     In order for the resulting log message to be logged the `LogLevel`
1419     must be greater or equal than the `LogLevel` of the used `Logger`
1420     and must be greater or equal than the global `LogLevel` and the
1421     condition must be `true`.
1422 
1423     Params:
1424       ll = The specific `LogLevel` used for logging the log message.
1425       condition = The condition must be `true` for the data to be logged.
1426       msg = The format string used for this log call.
1427       args = The data that should be logged.
1428 
1429     Example:
1430     --------------------
1431     auto s = new FileLogger(stdout);
1432     s.logf(LogLevel.trace, true ,"%d %s", 1337, "is number");
1433     s.logf(LogLevel.info, true ,"%d %s", 1337, "is number");
1434     s.logf(LogLevel.warning, true ,"%d %s", 1337, "is number");
1435     s.logf(LogLevel.error, false ,"%d %s", 1337, "is number");
1436     s.logf(LogLevel.fatal, true ,"%d %s", 1337, "is number");
1437     --------------------
1438     */
1439     void logf(int line = __LINE__, string file = __FILE__,
1440         string funcName = __FUNCTION__,
1441         string prettyFuncName = __PRETTY_FUNCTION__,
1442         string moduleName = __MODULE__, A...)(const LogLevel ll,
1443         lazy bool condition, lazy string msg, lazy A args)
1444     {
1445         static if (isLoggingActive) synchronized (mutex)
1446         {
1447             import std.format : formattedWrite;
1448 
1449             if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition))
1450             {
1451                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1452                     moduleName, ll, thisTid, Clock.currTime, this);
1453 
1454                 auto writer = MsgRange(this);
1455                 formattedWrite(writer, msg, args);
1456 
1457                 this.finishLogMsg();
1458 
1459                 if (ll == LogLevel.fatal)
1460                     this.fatalHandler_();
1461             }
1462         }
1463     }
1464 
1465     /** This function logs data to the used `Logger` with a specific
1466     `LogLevel` in a `printf`-style manner.
1467 
1468     In order for the resulting log message to be logged the `LogLevel`
1469     must be greater or equal than the `LogLevel` of the used `Logger`
1470     and must be greater or equal than the global `LogLevel`.
1471 
1472     Params:
1473       ll = The specific `LogLevel` used for logging the log message.
1474       msg = The format string used for this log call.
1475       args = The data that should be logged.
1476 
1477     Example:
1478     --------------------
1479     auto s = new FileLogger(stdout);
1480     s.logf(LogLevel.trace, "%d %s", 1337, "is number");
1481     s.logf(LogLevel.info, "%d %s", 1337, "is number");
1482     s.logf(LogLevel.warning, "%d %s", 1337, "is number");
1483     s.logf(LogLevel.error, "%d %s", 1337, "is number");
1484     s.logf(LogLevel.fatal, "%d %s", 1337, "is number");
1485     --------------------
1486     */
1487     void logf(int line = __LINE__, string file = __FILE__,
1488         string funcName = __FUNCTION__,
1489         string prettyFuncName = __PRETTY_FUNCTION__,
1490         string moduleName = __MODULE__, A...)(const LogLevel ll,
1491             lazy string msg, lazy A args)
1492     {
1493         static if (isLoggingActive) synchronized (mutex)
1494         {
1495             import std.format : formattedWrite;
1496 
1497             if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel))
1498             {
1499                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1500                     moduleName, ll, thisTid, Clock.currTime, this);
1501 
1502                 auto writer = MsgRange(this);
1503                 formattedWrite(writer, msg, args);
1504 
1505                 this.finishLogMsg();
1506 
1507                 if (ll == LogLevel.fatal)
1508                     this.fatalHandler_();
1509             }
1510         }
1511     }
1512 
1513     /** This function logs data to the used `Logger` depending on a
1514     condition with the `LogLevel` of the used `Logger` in a
1515     `printf`-style manner.
1516 
1517     In order for the resulting log message to be logged the `LogLevel`
1518     of the used `Logger` must be greater or equal than the global
1519     `LogLevel` and the condition must be `true`.
1520 
1521     Params:
1522       condition = The condition must be `true` for the data to be logged.
1523       msg = The format string used for this log call.
1524       args = The data that should be logged.
1525 
1526     Example:
1527     --------------------
1528     auto s = new FileLogger(stdout);
1529     s.logf(true ,"%d %s", 1337, "is number");
1530     s.logf(true ,"%d %s", 1337, "is number");
1531     s.logf(true ,"%d %s", 1337, "is number");
1532     s.logf(false ,"%d %s", 1337, "is number");
1533     s.logf(true ,"%d %s", 1337, "is number");
1534     --------------------
1535     */
1536     void logf(int line = __LINE__, string file = __FILE__,
1537         string funcName = __FUNCTION__,
1538         string prettyFuncName = __PRETTY_FUNCTION__,
1539         string moduleName = __MODULE__, A...)(lazy bool condition,
1540             lazy string msg, lazy A args)
1541     {
1542         static if (isLoggingActive) synchronized (mutex)
1543         {
1544             import std.format : formattedWrite;
1545 
1546             if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel,
1547                 condition))
1548             {
1549                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1550                     moduleName, this.logLevel_, thisTid, Clock.currTime, this);
1551 
1552                 auto writer = MsgRange(this);
1553                 formattedWrite(writer, msg, args);
1554 
1555                 this.finishLogMsg();
1556 
1557                 if (this.logLevel_ == LogLevel.fatal)
1558                     this.fatalHandler_();
1559             }
1560         }
1561     }
1562 
1563     /** This method logs data to the used `Logger` with the `LogLevel`
1564     of the this `Logger` in a `printf`-style manner.
1565 
1566     In order for the data to be processed the `LogLevel` of the `Logger`
1567     must be greater or equal to the global `LogLevel`.
1568 
1569     Params:
1570       msg = The format string used for this log call.
1571       args = The data that should be logged.
1572 
1573     Example:
1574     --------------------
1575     auto s = new FileLogger(stdout);
1576     s.logf("%d %s", 1337, "is number");
1577     s.logf("%d %s", 1337, "is number");
1578     s.logf("%d %s", 1337, "is number");
1579     s.logf("%d %s", 1337, "is number");
1580     s.logf("%d %s", 1337, "is number");
1581     --------------------
1582     */
1583     void logf(int line = __LINE__, string file = __FILE__,
1584         string funcName = __FUNCTION__,
1585         string prettyFuncName = __PRETTY_FUNCTION__,
1586         string moduleName = __MODULE__, A...)(lazy string msg, lazy A args)
1587     {
1588         static if (isLoggingActive) synchronized (mutex)
1589         {
1590             import std.format : formattedWrite;
1591 
1592             if (isLoggingEnabled(this.logLevel_, this.logLevel_,
1593                 globalLogLevel))
1594             {
1595                 this.beginLogMsg(file, line, funcName, prettyFuncName,
1596                     moduleName, this.logLevel_, thisTid, Clock.currTime, this);
1597 
1598                 auto writer = MsgRange(this);
1599                 formattedWrite(writer, msg, args);
1600 
1601                 this.finishLogMsg();
1602 
1603                 if (this.logLevel_ == LogLevel.fatal)
1604                     this.fatalHandler_();
1605             }
1606         }
1607     }
1608 
1609     private void delegate() @safe fatalHandler_;
1610     private shared LogLevel logLevel_ = LogLevel.info;
1611     private Mutex mutex;
1612 
1613     protected Appender!string msgAppender;
1614     protected LogEntry header;
1615 }
1616 
1617 // Thread Global
1618 
1619 private __gshared Logger stdSharedDefaultLogger;
1620 private shared Logger stdSharedLogger;
1621 private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all;
1622 
1623 /* This method returns the global default Logger.
1624  * Marked @trusted because of excessive reliance on __gshared data
1625  */
1626 private @property Logger defaultSharedLoggerImpl() @trusted
1627 {
1628     import std.conv : emplace;
1629     import std.stdio : stderr;
1630 
1631     __gshared align(FileLogger.alignof) void[__traits(classInstanceSize, FileLogger)] _buffer;
1632 
1633     import std.concurrency : initOnce;
1634     initOnce!stdSharedDefaultLogger({
1635         auto buffer = cast(ubyte[]) _buffer;
1636         return emplace!FileLogger(buffer, stderr, LogLevel.all);
1637     }());
1638 
1639     return stdSharedDefaultLogger;
1640 }
1641 
1642 /** This property sets and gets the default `Logger`.
1643 
1644 Example:
1645 -------------
1646 sharedLog = new FileLogger(yourFile);
1647 -------------
1648 The example sets a new `FileLogger` as new `sharedLog`.
1649 
1650 If at some point you want to use the original default logger again, you can
1651 use $(D sharedLog = null;). This will put back the original.
1652 
1653 Note:
1654 While getting and setting `sharedLog` is thread-safe, it has to be considered
1655 that the returned reference is only a current snapshot and in the following
1656 code, you must make sure no other thread reassigns to it between reading and
1657 writing `sharedLog`.
1658 
1659 `sharedLog` is only thread-safe if the the used `Logger` is thread-safe.
1660 The default `Logger` is thread-safe.
1661 -------------
1662 if (sharedLog !is myLogger)
1663     sharedLog = new myLogger;
1664 -------------
1665 */
1666 @property Logger sharedLog() @safe
1667 {
1668     static auto trustedLoad(ref shared Logger logger) @trusted
1669     {
1670         import core.atomic : atomicLoad, MemoryOrder;
1671         return cast() atomicLoad!(MemoryOrder.acq)(logger);
1672             //FIXME: Casting shared away here. Not good. See issue 16232.
1673     }
1674 
1675     // If we have set up our own logger use that
1676     if (auto logger = trustedLoad(stdSharedLogger))
1677     {
1678         return logger;
1679     }
1680     else
1681     {
1682         // Otherwise resort to the default logger
1683         return defaultSharedLoggerImpl;
1684     }
1685 }
1686 
1687 /// Ditto
1688 @property void sharedLog(Logger logger) @trusted
1689 {
1690     import core.atomic : atomicStore, MemoryOrder;
1691     atomicStore!(MemoryOrder.rel)(stdSharedLogger, cast(shared) logger);
1692 }
1693 
1694 /** This methods get and set the global `LogLevel`.
1695 
1696 Every log message with a `LogLevel` lower as the global `LogLevel`
1697 will be discarded before it reaches `writeLogMessage` method of any
1698 `Logger`.
1699 */
1700 /* Implementation note:
1701 For any public logging call, the global log level shall only be queried once on
1702 entry. Otherwise when another threads changes the level, we would work with
1703 different levels at different spots in the code.
1704 */
1705 @property LogLevel globalLogLevel() @safe @nogc
1706 {
1707     return trustedLoad(stdLoggerGlobalLogLevel);
1708 }
1709 
1710 /// Ditto
1711 @property void globalLogLevel(LogLevel ll) @safe
1712 {
1713     trustedStore(stdLoggerGlobalLogLevel, ll);
1714 }
1715 
1716 // Thread Local
1717 
1718 /** The `StdForwardLogger` will always forward anything to the sharedLog.
1719 
1720 The `StdForwardLogger` will not throw if data is logged with $(D
1721 LogLevel.fatal).
1722 */
1723 class StdForwardLogger : Logger
1724 {
1725     /** The default constructor for the `StdForwardLogger`.
1726 
1727     Params:
1728       lv = The `LogLevel` for the `MultiLogger`. By default the $(D
1729           LogLevel) is `all`.
1730     */
1731     this(const LogLevel lv = LogLevel.all) @safe
1732     {
1733         super(lv);
1734         this.fatalHandler = delegate() {};
1735     }
1736 
1737     override protected void writeLogMsg(ref LogEntry payload)
1738     {
1739           sharedLog.forwardMsg(payload);
1740     }
1741 }
1742 
1743 ///
1744 @safe unittest
1745 {
1746     auto nl1 = new StdForwardLogger(LogLevel.all);
1747 }
1748 
1749 /** This `LogLevel` is unqiue to every thread.
1750 
1751 The thread local `Logger` will use this `LogLevel` to filter log calls
1752 every same way as presented earlier.
1753 */
1754 //public LogLevel threadLogLevel = LogLevel.all;
1755 private Logger stdLoggerThreadLogger;
1756 private Logger stdLoggerDefaultThreadLogger;
1757 
1758 /* This method returns the thread local default Logger.
1759 */
1760 private @property Logger stdThreadLocalLogImpl() @trusted
1761 {
1762     import std.conv : emplace;
1763 
1764     static void*[(__traits(classInstanceSize, StdForwardLogger) - 1) / (void*).sizeof + 1] _buffer;
1765 
1766     auto buffer = cast(ubyte[]) _buffer;
1767 
1768     if (stdLoggerDefaultThreadLogger is null)
1769     {
1770         stdLoggerDefaultThreadLogger = emplace!StdForwardLogger(buffer, LogLevel.all);
1771     }
1772     return stdLoggerDefaultThreadLogger;
1773 }
1774 
1775 /** This function returns a thread unique `Logger`, that by default
1776 propergates all data logged to it to the `sharedLog`.
1777 
1778 These properties can be used to set and get this `Logger`. Every
1779 modification to this `Logger` will only be visible in the thread the
1780 modification has been done from.
1781 
1782 This `Logger` is called by the free standing log functions. This allows to
1783 create thread local redirections and still use the free standing log
1784 functions.
1785 */
1786 @property Logger stdThreadLocalLog() @safe
1787 {
1788     // If we have set up our own logger use that
1789     if (auto logger = stdLoggerThreadLogger)
1790         return logger;
1791     else
1792         // Otherwise resort to the default logger
1793         return stdThreadLocalLogImpl;
1794 }
1795 
1796 /// Ditto
1797 @property void stdThreadLocalLog(Logger logger) @safe
1798 {
1799     stdLoggerThreadLogger = logger;
1800 }
1801 
1802 /// Ditto
1803 @system unittest
1804 {
1805     import std.experimental.logger.filelogger : FileLogger;
1806     import std.file : deleteme, remove;
1807     Logger l = stdThreadLocalLog;
1808     stdThreadLocalLog = new FileLogger(deleteme ~ "-someFile.log");
1809     scope(exit) remove(deleteme ~ "-someFile.log");
1810 
1811     auto tempLog = stdThreadLocalLog;
1812     stdThreadLocalLog = l;
1813     destroy(tempLog);
1814 }
1815 
1816 @safe unittest
1817 {
1818     LogLevel ll = globalLogLevel;
1819     globalLogLevel = LogLevel.fatal;
1820     assert(globalLogLevel == LogLevel.fatal);
1821     globalLogLevel = ll;
1822 }
1823 
1824 package class TestLogger : Logger
1825 {
1826     int line = -1;
1827     string file = null;
1828     string func = null;
1829     string prettyFunc = null;
1830     string msg = null;
1831     LogLevel lvl;
1832 
1833     this(const LogLevel lv = LogLevel.all) @safe
1834     {
1835         super(lv);
1836     }
1837 
1838     override protected void writeLogMsg(ref LogEntry payload) @safe
1839     {
1840         this.line = payload.line;
1841         this.file = payload.file;
1842         this.func = payload.funcName;
1843         this.prettyFunc = payload.prettyFuncName;
1844         this.lvl = payload.logLevel;
1845         this.msg = payload.msg;
1846     }
1847 }
1848 
1849 version (StdUnittest) private void testFuncNames(Logger logger) @safe
1850 {
1851     string s = "I'm here";
1852     logger.log(s);
1853 }
1854 
1855 @safe unittest
1856 {
1857     auto tl1 = new TestLogger();
1858     testFuncNames(tl1);
1859     assert(tl1.func == "std.experimental.logger.core.testFuncNames", tl1.func);
1860     assert(tl1.prettyFunc ==
1861         "void std.experimental.logger.core.testFuncNames(Logger logger) @safe",
1862         tl1.prettyFunc);
1863     assert(tl1.msg == "I'm here", tl1.msg);
1864 }
1865 
1866 @safe unittest
1867 {
1868     auto tl1 = new TestLogger(LogLevel.all);
1869     tl1.log();
1870     assert(tl1.line == __LINE__ - 1);
1871     tl1.log(true);
1872     assert(tl1.line == __LINE__ - 1);
1873     tl1.log(false);
1874     assert(tl1.line == __LINE__ - 3);
1875     tl1.log(LogLevel.info);
1876     assert(tl1.line == __LINE__ - 1);
1877     tl1.log(LogLevel.off);
1878     assert(tl1.line == __LINE__ - 3);
1879     tl1.log(LogLevel.info, true);
1880     assert(tl1.line == __LINE__ - 1);
1881     tl1.log(LogLevel.info, false);
1882     assert(tl1.line == __LINE__ - 3);
1883 
1884     auto oldunspecificLogger = sharedLog;
1885     scope(exit) {
1886         sharedLog = oldunspecificLogger;
1887     }
1888 
1889     sharedLog = tl1;
1890 
1891     log();
1892     assert(tl1.line == __LINE__ - 1);
1893 
1894     log(LogLevel.info);
1895     assert(tl1.line == __LINE__ - 1);
1896 
1897     log(true);
1898     assert(tl1.line == __LINE__ - 1);
1899 
1900     log(LogLevel.warning, true);
1901     assert(tl1.line == __LINE__ - 1);
1902 
1903     trace();
1904     assert(tl1.line == __LINE__ - 1);
1905 }
1906 
1907 @safe unittest
1908 {
1909     import std.experimental.logger.multilogger : MultiLogger;
1910 
1911     auto tl1 = new TestLogger;
1912     auto tl2 = new TestLogger;
1913 
1914     auto ml = new MultiLogger();
1915     ml.insertLogger("one", tl1);
1916     ml.insertLogger("two", tl2);
1917 
1918     string msg = "Hello Logger World";
1919     ml.log(msg);
1920     int lineNumber = __LINE__ - 1;
1921     assert(tl1.msg == msg);
1922     assert(tl1.line == lineNumber);
1923     assert(tl2.msg == msg);
1924     assert(tl2.line == lineNumber);
1925 
1926     ml.removeLogger("one");
1927     ml.removeLogger("two");
1928     auto n = ml.removeLogger("one");
1929     assert(n is null);
1930 }
1931 
1932 @safe unittest
1933 {
1934     bool errorThrown = false;
1935     auto tl = new TestLogger;
1936     auto dele = delegate() {
1937         errorThrown = true;
1938     };
1939     tl.fatalHandler = dele;
1940     tl.fatal();
1941     assert(errorThrown);
1942 }
1943 
1944 @safe unittest
1945 {
1946     import std.conv : to;
1947     import std.exception : assertThrown, assertNotThrown;
1948     import std.format : format;
1949 
1950     auto l = new TestLogger(LogLevel.all);
1951     string msg = "Hello Logger World";
1952     l.log(msg);
1953     int lineNumber = __LINE__ - 1;
1954     assert(l.msg == msg);
1955     assert(l.line == lineNumber);
1956     assert(l.logLevel == LogLevel.all);
1957 
1958     l.log(true, msg);
1959     lineNumber = __LINE__ - 1;
1960     assert(l.msg == msg, l.msg);
1961     assert(l.line == lineNumber);
1962     assert(l.logLevel == LogLevel.all);
1963 
1964     l.log(false, msg);
1965     assert(l.msg == msg);
1966     assert(l.line == lineNumber, to!string(l.line));
1967     assert(l.logLevel == LogLevel.all);
1968 
1969     msg = "%s Another message";
1970     l.logf(msg, "Yet");
1971     lineNumber = __LINE__ - 1;
1972     assert(l.msg == msg.format("Yet"));
1973     assert(l.line == lineNumber);
1974     assert(l.logLevel == LogLevel.all);
1975 
1976     l.logf(true, msg, "Yet");
1977     lineNumber = __LINE__ - 1;
1978     assert(l.msg == msg.format("Yet"));
1979     assert(l.line == lineNumber);
1980     assert(l.logLevel == LogLevel.all);
1981 
1982     l.logf(false, msg, "Yet");
1983     assert(l.msg == msg.format("Yet"));
1984     assert(l.line == lineNumber);
1985     assert(l.logLevel == LogLevel.all);
1986 
1987     () @trusted {
1988         assertThrown!Throwable(l.logf(LogLevel.fatal, msg, "Yet"));
1989     } ();
1990     lineNumber = __LINE__ - 2;
1991     assert(l.msg == msg.format("Yet"));
1992     assert(l.line == lineNumber);
1993     assert(l.logLevel == LogLevel.all);
1994 
1995     () @trusted {
1996         assertThrown!Throwable(l.logf(LogLevel.fatal, true, msg, "Yet"));
1997     } ();
1998     lineNumber = __LINE__ - 2;
1999     assert(l.msg == msg.format("Yet"));
2000     assert(l.line == lineNumber);
2001     assert(l.logLevel == LogLevel.all);
2002 
2003     assertNotThrown(l.logf(LogLevel.fatal, false, msg, "Yet"));
2004     assert(l.msg == msg.format("Yet"));
2005     assert(l.line == lineNumber);
2006     assert(l.logLevel == LogLevel.all);
2007 
2008     auto oldunspecificLogger = sharedLog;
2009 
2010     assert(oldunspecificLogger.logLevel == LogLevel.all,
2011          to!string(oldunspecificLogger.logLevel));
2012 
2013     assert(l.logLevel == LogLevel.all);
2014     sharedLog = l;
2015     assert(globalLogLevel == LogLevel.all,
2016             to!string(globalLogLevel));
2017 
2018     scope(exit)
2019     {
2020         sharedLog = oldunspecificLogger;
2021     }
2022 
2023     assert(sharedLog.logLevel == LogLevel.all);
2024     assert(stdThreadLocalLog.logLevel == LogLevel.all);
2025     assert(globalLogLevel == LogLevel.all);
2026 
2027     msg = "Another message";
2028     log(msg);
2029     lineNumber = __LINE__ - 1;
2030     assert(l.logLevel == LogLevel.all);
2031     assert(l.line == lineNumber, to!string(l.line));
2032     assert(l.msg == msg, l.msg);
2033 
2034     log(true, msg);
2035     lineNumber = __LINE__ - 1;
2036     assert(l.msg == msg);
2037     assert(l.line == lineNumber);
2038     assert(l.logLevel == LogLevel.all);
2039 
2040     log(false, msg);
2041     assert(l.msg == msg);
2042     assert(l.line == lineNumber);
2043     assert(l.logLevel == LogLevel.all);
2044 
2045     msg = "%s Another message";
2046     logf(msg, "Yet");
2047     lineNumber = __LINE__ - 1;
2048     assert(l.msg == msg.format("Yet"));
2049     assert(l.line == lineNumber);
2050     assert(l.logLevel == LogLevel.all);
2051 
2052     logf(true, msg, "Yet");
2053     lineNumber = __LINE__ - 1;
2054     assert(l.msg == msg.format("Yet"));
2055     assert(l.line == lineNumber);
2056     assert(l.logLevel == LogLevel.all);
2057 
2058     logf(false, msg, "Yet");
2059     assert(l.msg == msg.format("Yet"));
2060     assert(l.line == lineNumber);
2061     assert(l.logLevel == LogLevel.all);
2062 
2063     msg = "%s Another message";
2064     () @trusted {
2065         assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet"));
2066     } ();
2067     lineNumber = __LINE__ - 2;
2068     assert(l.msg == msg.format("Yet"));
2069     assert(l.line == lineNumber);
2070     assert(l.logLevel == LogLevel.all);
2071 
2072     () @trusted {
2073         assertThrown!Throwable(logf(LogLevel.fatal, true, msg, "Yet"));
2074     } ();
2075     lineNumber = __LINE__ - 2;
2076     assert(l.msg == msg.format("Yet"));
2077     assert(l.line == lineNumber);
2078     assert(l.logLevel == LogLevel.all);
2079 
2080     assertNotThrown(logf(LogLevel.fatal, false, msg, "Yet"));
2081     assert(l.msg == msg.format("Yet"));
2082     assert(l.line == lineNumber);
2083     assert(l.logLevel == LogLevel.all);
2084 }
2085 
2086 @system unittest // default logger
2087 {
2088     import std.file : deleteme, exists, remove;
2089     import std.stdio : File;
2090     import std..string : indexOf;
2091 
2092     string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
2093     FileLogger l = new FileLogger(filename);
2094     auto oldunspecificLogger = sharedLog;
2095     sharedLog = l;
2096 
2097     scope(exit)
2098     {
2099         remove(filename);
2100         assert(!exists(filename));
2101         sharedLog = oldunspecificLogger;
2102         globalLogLevel = LogLevel.all;
2103     }
2104 
2105     string notWritten = "this should not be written to file";
2106     string written = "this should be written to file";
2107 
2108     globalLogLevel = LogLevel.critical;
2109     assert(globalLogLevel == LogLevel.critical);
2110 
2111     log(LogLevel.warning, notWritten);
2112     log(LogLevel.critical, written);
2113 
2114     l.file.flush();
2115     l.file.close();
2116 
2117     auto file = File(filename, "r");
2118     assert(!file.eof);
2119 
2120     string readLine = file.readln();
2121     assert(readLine.indexOf(written) != -1, readLine);
2122     assert(readLine.indexOf(notWritten) == -1, readLine);
2123     file.close();
2124 }
2125 
2126 @system unittest
2127 {
2128     import std.file : deleteme, remove;
2129     import std.stdio : File;
2130     import std..string : indexOf;
2131 
2132     string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
2133     auto oldunspecificLogger = sharedLog;
2134 
2135     scope(exit)
2136     {
2137         remove(filename);
2138         sharedLog = oldunspecificLogger;
2139         globalLogLevel = LogLevel.all;
2140     }
2141 
2142     string notWritten = "this should not be written to file";
2143     string written = "this should be written to file";
2144 
2145     auto l = new FileLogger(filename);
2146     sharedLog = l;
2147     sharedLog.logLevel = LogLevel.critical;
2148 
2149     log(LogLevel.error, false, notWritten);
2150     log(LogLevel.critical, true, written);
2151     destroy(l);
2152 
2153     auto file = File(filename, "r");
2154     auto readLine = file.readln();
2155     assert(!readLine.empty, readLine);
2156     assert(readLine.indexOf(written) != -1);
2157     assert(readLine.indexOf(notWritten) == -1);
2158     file.close();
2159 }
2160 
2161 @safe unittest
2162 {
2163     import std.conv : to;
2164 
2165     auto tl = new TestLogger(LogLevel.all);
2166     int l = __LINE__;
2167     tl.info("a");
2168     assert(tl.line == l+1);
2169     assert(tl.msg == "a");
2170     assert(tl.logLevel == LogLevel.all);
2171     assert(globalLogLevel == LogLevel.all);
2172     l = __LINE__;
2173     tl.trace("b");
2174     assert(tl.msg == "b", tl.msg);
2175     assert(tl.line == l+1, to!string(tl.line));
2176 }
2177 
2178 // testing possible log conditions
2179 @safe unittest
2180 {
2181     import std.conv : to;
2182     import std.format : format;
2183     import std..string : indexOf;
2184 
2185     auto oldunspecificLogger = sharedLog;
2186 
2187     auto mem = new TestLogger;
2188     mem.fatalHandler = delegate() {};
2189     sharedLog = mem;
2190 
2191     scope(exit)
2192     {
2193         sharedLog = oldunspecificLogger;
2194         globalLogLevel = LogLevel.all;
2195     }
2196 
2197     int value = 0;
2198     foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2199             LogLevel.info, LogLevel.warning, LogLevel.error,
2200             LogLevel.critical, LogLevel.fatal, LogLevel.off])
2201     {
2202 
2203         globalLogLevel = gll;
2204 
2205         foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2206                 LogLevel.info, LogLevel.warning, LogLevel.error,
2207                 LogLevel.critical, LogLevel.fatal, LogLevel.off])
2208         {
2209 
2210             mem.logLevel = ll;
2211 
2212             foreach (cond; [true, false])
2213             {
2214                 foreach (condValue; [true, false])
2215                 {
2216                     foreach (memOrG; [true, false])
2217                     {
2218                         foreach (prntf; [true, false])
2219                         {
2220                             foreach (ll2; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2221                                     LogLevel.info, LogLevel.warning,
2222                                     LogLevel.error, LogLevel.critical,
2223                                     LogLevel.fatal, LogLevel.off])
2224                             {
2225                                 foreach (singleMulti; 0 .. 2)
2226                                 {
2227                                     int lineCall;
2228                                     mem.msg = "-1";
2229                                     if (memOrG)
2230                                     {
2231                                         if (prntf)
2232                                         {
2233                                             if (cond)
2234                                             {
2235                                                 if (singleMulti == 0)
2236                                                 {
2237                                                     mem.logf(ll2, condValue, "%s",
2238                                                         value);
2239                                                     lineCall = __LINE__;
2240                                                 }
2241                                                 else
2242                                                 {
2243                                                     mem.logf(ll2, condValue,
2244                                                         "%d %d", value, value);
2245                                                     lineCall = __LINE__;
2246                                                 }
2247                                             }
2248                                             else
2249                                             {
2250                                                 if (singleMulti == 0)
2251                                                 {
2252                                                     mem.logf(ll2, "%s", value);
2253                                                     lineCall = __LINE__;
2254                                                 }
2255                                                 else
2256                                                 {
2257                                                     mem.logf(ll2, "%d %d",
2258                                                         value, value);
2259                                                     lineCall = __LINE__;
2260                                                 }
2261                                             }
2262                                         }
2263                                         else
2264                                         {
2265                                             if (cond)
2266                                             {
2267                                                 if (singleMulti == 0)
2268                                                 {
2269                                                     mem.log(ll2, condValue,
2270                                                         to!string(value));
2271                                                     lineCall = __LINE__;
2272                                                 }
2273                                                 else
2274                                                 {
2275                                                     mem.log(ll2, condValue,
2276                                                         to!string(value), value);
2277                                                     lineCall = __LINE__;
2278                                                 }
2279                                             }
2280                                             else
2281                                             {
2282                                                 if (singleMulti == 0)
2283                                                 {
2284                                                     mem.log(ll2, to!string(value));
2285                                                     lineCall = __LINE__;
2286                                                 }
2287                                                 else
2288                                                 {
2289                                                     mem.log(ll2,
2290                                                         to!string(value),
2291                                                         value);
2292                                                     lineCall = __LINE__;
2293                                                 }
2294                                             }
2295                                         }
2296                                     }
2297                                     else
2298                                     {
2299                                         if (prntf)
2300                                         {
2301                                             if (cond)
2302                                             {
2303                                                 if (singleMulti == 0)
2304                                                 {
2305                                                     logf(ll2, condValue, "%s",
2306                                                         value);
2307                                                     lineCall = __LINE__;
2308                                                 }
2309                                                 else
2310                                                 {
2311                                                     logf(ll2, condValue,
2312                                                         "%s %d", value, value);
2313                                                     lineCall = __LINE__;
2314                                                 }
2315                                             }
2316                                             else
2317                                             {
2318                                                 if (singleMulti == 0)
2319                                                 {
2320                                                     logf(ll2, "%s", value);
2321                                                     lineCall = __LINE__;
2322                                                 }
2323                                                 else
2324                                                 {
2325                                                     logf(ll2, "%s %s", value,
2326                                                         value);
2327                                                     lineCall = __LINE__;
2328                                                 }
2329                                             }
2330                                         }
2331                                         else
2332                                         {
2333                                             if (cond)
2334                                             {
2335                                                 if (singleMulti == 0)
2336                                                 {
2337                                                     log(ll2, condValue,
2338                                                         to!string(value));
2339                                                     lineCall = __LINE__;
2340                                                 }
2341                                                 else
2342                                                 {
2343                                                     log(ll2, condValue, value,
2344                                                         to!string(value));
2345                                                     lineCall = __LINE__;
2346                                                 }
2347                                             }
2348                                             else
2349                                             {
2350                                                 if (singleMulti == 0)
2351                                                 {
2352                                                     log(ll2, to!string(value));
2353                                                     lineCall = __LINE__;
2354                                                 }
2355                                                 else
2356                                                 {
2357                                                     log(ll2, value,
2358                                                         to!string(value));
2359                                                     lineCall = __LINE__;
2360                                                 }
2361                                             }
2362                                         }
2363                                     }
2364 
2365                                     string valueStr = to!string(value);
2366                                     ++value;
2367 
2368                                     bool ll2Off = (ll2 != LogLevel.off);
2369                                     bool gllOff = (gll != LogLevel.off);
2370                                     bool llOff = (ll != LogLevel.off);
2371                                     bool condFalse = (cond ? condValue : true);
2372                                     bool ll2VSgll = (ll2 >= gll);
2373                                     bool ll2VSll = (ll2 >= ll);
2374 
2375                                     bool shouldLog = ll2Off && gllOff && llOff
2376                                         && condFalse && ll2VSgll && ll2VSll;
2377 
2378                                     /*
2379                                     writefln(
2380                                         "go(%b) ll2o(%b) c(%b) lg(%b) ll(%b) s(%b)"
2381                                         , gll != LogLevel.off, ll2 != LogLevel.off,
2382                                         cond ? condValue : true,
2383                                         ll2 >= gll, ll2 >= ll, shouldLog);
2384                                     */
2385 
2386 
2387                                     if (shouldLog)
2388                                     {
2389                                         assert(mem.msg.indexOf(valueStr) != -1,
2390                                             format(
2391                                             "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~
2392                                             "cond(%b) condValue(%b)" ~
2393                                             " memOrG(%b) shouldLog(%b) %s == %s" ~
2394                                             " %b %b %b %b %b",
2395                                             lineCall, ll2Off, gll, ll, ll2, cond,
2396                                             condValue, memOrG, shouldLog, mem.msg,
2397                                             valueStr, gllOff, llOff, condFalse,
2398                                             ll2VSgll, ll2VSll
2399                                         ));
2400                                     }
2401                                     else
2402                                     {
2403                                         assert(mem.msg.indexOf(valueStr),
2404                                             format(
2405                                             "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~
2406                                             "cond(%b) condValue(%b)" ~
2407                                             " memOrG(%b) shouldLog(%b) %s == %s" ~
2408                                             " %b %b %b %b %b",
2409                                             lineCall, ll2Off, gll, ll, ll2, cond,
2410                                             condValue, memOrG, shouldLog, mem.msg,
2411                                             valueStr, gllOff, llOff, condFalse,
2412                                             ll2VSgll, ll2VSll
2413                                         ));
2414                                     }
2415                                 }
2416                             }
2417                         }
2418                     }
2419                 }
2420             }
2421         }
2422     }
2423 }
2424 
2425 // more testing
2426 @safe unittest
2427 {
2428     import std.conv : to;
2429     import std.format : format;
2430     import std..string : indexOf;
2431 
2432     auto oldunspecificLogger = sharedLog;
2433 
2434     auto mem = new TestLogger;
2435     mem.fatalHandler = delegate() {};
2436     sharedLog = mem;
2437 
2438     scope(exit)
2439     {
2440         sharedLog = oldunspecificLogger;
2441         globalLogLevel = LogLevel.all;
2442     }
2443 
2444     int value = 0;
2445     foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2446             LogLevel.info, LogLevel.warning, LogLevel.error,
2447             LogLevel.critical, LogLevel.fatal, LogLevel.off])
2448     {
2449 
2450         globalLogLevel = gll;
2451 
2452         foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2453                 LogLevel.info, LogLevel.warning, LogLevel.error,
2454                 LogLevel.critical, LogLevel.fatal, LogLevel.off])
2455         {
2456             mem.logLevel = ll;
2457 
2458             foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2459                     LogLevel.info, LogLevel.warning, LogLevel.error,
2460                     LogLevel.critical, LogLevel.fatal, LogLevel.off])
2461             {
2462                 stdThreadLocalLog.logLevel = tll;
2463 
2464                 foreach (cond; [true, false])
2465                 {
2466                     foreach (condValue; [true, false])
2467                     {
2468                         foreach (memOrG; [true, false])
2469                         {
2470                             foreach (prntf; [true, false])
2471                             {
2472                                 foreach (singleMulti; 0 .. 2)
2473                                 {
2474                                     int lineCall;
2475                                     mem.msg = "-1";
2476                                     if (memOrG)
2477                                     {
2478                                         if (prntf)
2479                                         {
2480                                             if (cond)
2481                                             {
2482                                                 if (singleMulti == 0)
2483                                                 {
2484                                                     mem.logf(condValue, "%s",
2485                                                         value);
2486                                                     lineCall = __LINE__;
2487                                                 }
2488                                                 else
2489                                                 {
2490                                                     mem.logf(condValue,
2491                                                         "%d %d", value, value);
2492                                                     lineCall = __LINE__;
2493                                                 }
2494                                             }
2495                                             else
2496                                             {
2497                                                 if (singleMulti == 0)
2498                                                 {
2499                                                     mem.logf("%s", value);
2500                                                     lineCall = __LINE__;
2501                                                 }
2502                                                 else
2503                                                 {
2504                                                     mem.logf("%d %d",
2505                                                         value, value);
2506                                                     lineCall = __LINE__;
2507                                                 }
2508                                             }
2509                                         }
2510                                         else
2511                                         {
2512                                             if (cond)
2513                                             {
2514                                                 if (singleMulti == 0)
2515                                                 {
2516                                                     mem.log(condValue,
2517                                                         to!string(value));
2518                                                     lineCall = __LINE__;
2519                                                 }
2520                                                 else
2521                                                 {
2522                                                     mem.log(condValue,
2523                                                         to!string(value), value);
2524                                                     lineCall = __LINE__;
2525                                                 }
2526                                             }
2527                                             else
2528                                             {
2529                                                 if (singleMulti == 0)
2530                                                 {
2531                                                     mem.log(to!string(value));
2532                                                     lineCall = __LINE__;
2533                                                 }
2534                                                 else
2535                                                 {
2536                                                     mem.log(to!string(value),
2537                                                         value);
2538                                                     lineCall = __LINE__;
2539                                                 }
2540                                             }
2541                                         }
2542                                     }
2543                                     else
2544                                     {
2545                                         if (prntf)
2546                                         {
2547                                             if (cond)
2548                                             {
2549                                                 if (singleMulti == 0)
2550                                                 {
2551                                                     logf(condValue, "%s", value);
2552                                                     lineCall = __LINE__;
2553                                                 }
2554                                                 else
2555                                                 {
2556                                                     logf(condValue, "%s %d", value,
2557                                                         value);
2558                                                     lineCall = __LINE__;
2559                                                 }
2560                                             }
2561                                             else
2562                                             {
2563                                                 if (singleMulti == 0)
2564                                                 {
2565                                                     logf("%s", value);
2566                                                     lineCall = __LINE__;
2567                                                 }
2568                                                 else
2569                                                 {
2570                                                     logf("%s %s", value, value);
2571                                                     lineCall = __LINE__;
2572                                                 }
2573                                             }
2574                                         }
2575                                         else
2576                                         {
2577                                             if (cond)
2578                                             {
2579                                                 if (singleMulti == 0)
2580                                                 {
2581                                                     log(condValue,
2582                                                         to!string(value));
2583                                                     lineCall = __LINE__;
2584                                                 }
2585                                                 else
2586                                                 {
2587                                                     log(condValue, value,
2588                                                         to!string(value));
2589                                                     lineCall = __LINE__;
2590                                                 }
2591                                             }
2592                                             else
2593                                             {
2594                                                 if (singleMulti == 0)
2595                                                 {
2596                                                     log(to!string(value));
2597                                                     lineCall = __LINE__;
2598                                                 }
2599                                                 else
2600                                                 {
2601                                                     log(value, to!string(value));
2602                                                     lineCall = __LINE__;
2603                                                 }
2604                                             }
2605                                         }
2606                                     }
2607 
2608                                     string valueStr = to!string(value);
2609                                     ++value;
2610 
2611                                     bool gllOff = (gll != LogLevel.off);
2612                                     bool llOff = (ll != LogLevel.off);
2613                                     bool tllOff = (tll != LogLevel.off);
2614                                     bool llVSgll = (ll >= gll);
2615                                     bool tllVSll =
2616                                         (stdThreadLocalLog.logLevel >= ll);
2617                                     bool condFalse = (cond ? condValue : true);
2618 
2619                                     bool shouldLog = gllOff && llOff
2620                                         && (memOrG ? true : tllOff)
2621                                         && (memOrG ?
2622                                             (ll >= gll) :
2623                                             (tll >= gll && tll >= ll))
2624                                         && condFalse;
2625 
2626                                     if (shouldLog)
2627                                     {
2628                                         assert(mem.msg.indexOf(valueStr) != -1,
2629                                             format("\ngll(%s) ll(%s) tll(%s) " ~
2630                                                 "cond(%s) condValue(%s) " ~
2631                                                 "memOrG(%s) prntf(%s) " ~
2632                                                 "singleMulti(%s)",
2633                                                 gll, ll, tll, cond, condValue,
2634                                                 memOrG, prntf, singleMulti)
2635                                             ~ format(" gllOff(%s) llOff(%s) " ~
2636                                                 "llVSgll(%s) tllVSll(%s) " ~
2637                                                 "tllOff(%s) condFalse(%s) "
2638                                                 ~ "shoudlLog(%s)",
2639                                                 gll != LogLevel.off,
2640                                                 ll != LogLevel.off, llVSgll,
2641                                                 tllVSll, tllOff, condFalse,
2642                                                 shouldLog)
2643                                             ~ format("msg(%s) line(%s) " ~
2644                                                 "lineCall(%s) valueStr(%s)",
2645                                                 mem.msg, mem.line, lineCall,
2646                                                 valueStr)
2647                                         );
2648                                     }
2649                                     else
2650                                     {
2651                                         assert(mem.msg.indexOf(valueStr) == -1,
2652                                             format("\ngll(%s) ll(%s) tll(%s) " ~
2653                                                 "cond(%s) condValue(%s) " ~
2654                                                 "memOrG(%s) prntf(%s) " ~
2655                                                 "singleMulti(%s)",
2656                                                 gll, ll, tll, cond, condValue,
2657                                                 memOrG, prntf, singleMulti)
2658                                             ~ format(" gllOff(%s) llOff(%s) " ~
2659                                                 "llVSgll(%s) tllVSll(%s) " ~
2660                                                 "tllOff(%s) condFalse(%s) "
2661                                                 ~ "shoudlLog(%s)",
2662                                                 gll != LogLevel.off,
2663                                                 ll != LogLevel.off, llVSgll,
2664                                                 tllVSll, tllOff, condFalse,
2665                                                 shouldLog)
2666                                             ~ format("msg(%s) line(%s) " ~
2667                                                 "lineCall(%s) valueStr(%s)",
2668                                                 mem.msg, mem.line, lineCall,
2669                                                 valueStr)
2670                                         );
2671                                     }
2672                                 }
2673                             }
2674                         }
2675                     }
2676                 }
2677             }
2678         }
2679     }
2680 }
2681 
2682 // testing more possible log conditions
2683 @safe unittest
2684 {
2685     bool fatalLog;
2686     auto mem = new TestLogger;
2687     mem.fatalHandler = delegate() { fatalLog = true; };
2688     auto oldunspecificLogger = sharedLog;
2689 
2690     stdThreadLocalLog.logLevel = LogLevel.all;
2691 
2692     sharedLog = mem;
2693     scope(exit)
2694     {
2695         sharedLog = oldunspecificLogger;
2696         globalLogLevel = LogLevel.all;
2697     }
2698 
2699     foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2700             LogLevel.info, LogLevel.warning, LogLevel.error,
2701             LogLevel.critical, LogLevel.fatal, LogLevel.off])
2702     {
2703 
2704         globalLogLevel = gll;
2705 
2706         foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2707                 LogLevel.info, LogLevel.warning, LogLevel.error,
2708                 LogLevel.critical, LogLevel.fatal, LogLevel.off])
2709         {
2710             mem.logLevel = ll;
2711 
2712             foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace,
2713                     LogLevel.info, LogLevel.warning, LogLevel.error,
2714                     LogLevel.critical, LogLevel.fatal, LogLevel.off])
2715             {
2716                 stdThreadLocalLog.logLevel = tll;
2717 
2718                 foreach (cond; [true, false])
2719                 {
2720                     assert(globalLogLevel == gll);
2721                     assert(mem.logLevel == ll);
2722 
2723                     bool gllVSll = LogLevel.trace >= globalLogLevel;
2724                     bool llVSgll = ll >= globalLogLevel;
2725                     bool lVSll = LogLevel.trace >= ll;
2726                     bool gllOff = globalLogLevel != LogLevel.off;
2727                     bool llOff = mem.logLevel != LogLevel.off;
2728                     bool tllOff = stdThreadLocalLog.logLevel != LogLevel.off;
2729                     bool tllVSll = tll >= ll;
2730                     bool tllVSgll = tll >= gll;
2731                     bool lVSgll = LogLevel.trace >= tll;
2732 
2733                     bool test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
2734                     bool testG = gllOff && llOff && tllOff && lVSgll && tllVSll && tllVSgll && cond;
2735 
2736                     mem.line = -1;
2737                     /*
2738                     writefln("gll(%3u) ll(%3u) cond(%b) test(%b)",
2739                         gll, ll, cond, test);
2740                     writefln("%b %b %b %b %b %b test2(%b)", llVSgll, gllVSll, lVSll,
2741                         gllOff, llOff, cond, test2);
2742                     */
2743 
2744                     mem.trace(__LINE__); int line = __LINE__;
2745                     assert(test ? mem.line == line : true); line = -1;
2746 
2747                     trace(__LINE__); line = __LINE__;
2748                     assert(testG ? mem.line == line : true); line = -1;
2749 
2750                     mem.trace(cond, __LINE__); line = __LINE__;
2751                     assert(test ? mem.line == line : true); line = -1;
2752 
2753                     trace(cond, __LINE__); line = __LINE__;
2754                     assert(testG ? mem.line == line : true); line = -1;
2755 
2756                     mem.tracef("%d", __LINE__); line = __LINE__;
2757                     assert(test ? mem.line == line : true); line = -1;
2758 
2759                     tracef("%d", __LINE__); line = __LINE__;
2760                     assert(testG ? mem.line == line : true); line = -1;
2761 
2762                     mem.tracef(cond, "%d", __LINE__); line = __LINE__;
2763                     assert(test ? mem.line == line : true); line = -1;
2764 
2765                     tracef(cond, "%d", __LINE__); line = __LINE__;
2766                     assert(testG ? mem.line == line : true); line = -1;
2767 
2768                     llVSgll = ll >= globalLogLevel;
2769                     lVSll = LogLevel.info >= ll;
2770                     lVSgll = LogLevel.info >= tll;
2771                     test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
2772                     testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
2773                         lVSgll && cond;
2774 
2775                     mem.info(__LINE__); line = __LINE__;
2776                     assert(test ? mem.line == line : true); line = -1;
2777 
2778                     info(__LINE__); line = __LINE__;
2779                     assert(testG ? mem.line == line : true); line = -1;
2780 
2781                     mem.info(cond, __LINE__); line = __LINE__;
2782                     assert(test ? mem.line == line : true); line = -1;
2783 
2784                     info(cond, __LINE__); line = __LINE__;
2785                     assert(testG ? mem.line == line : true); line = -1;
2786 
2787                     mem.infof("%d", __LINE__); line = __LINE__;
2788                     assert(test ? mem.line == line : true); line = -1;
2789 
2790                     infof("%d", __LINE__); line = __LINE__;
2791                     assert(testG ? mem.line == line : true); line = -1;
2792 
2793                     mem.infof(cond, "%d", __LINE__); line = __LINE__;
2794                     assert(test ? mem.line == line : true); line = -1;
2795 
2796                     infof(cond, "%d", __LINE__); line = __LINE__;
2797                     assert(testG ? mem.line == line : true); line = -1;
2798 
2799                     llVSgll = ll >= globalLogLevel;
2800                     lVSll = LogLevel.warning >= ll;
2801                     lVSgll = LogLevel.warning >= tll;
2802                     test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
2803                     testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
2804                         lVSgll && cond;
2805 
2806                     mem.warning(__LINE__); line = __LINE__;
2807                     assert(test ? mem.line == line : true); line = -1;
2808 
2809                     warning(__LINE__); line = __LINE__;
2810                     assert(testG ? mem.line == line : true); line = -1;
2811 
2812                     mem.warning(cond, __LINE__); line = __LINE__;
2813                     assert(test ? mem.line == line : true); line = -1;
2814 
2815                     warning(cond, __LINE__); line = __LINE__;
2816                     assert(testG ? mem.line == line : true); line = -1;
2817 
2818                     mem.warningf("%d", __LINE__); line = __LINE__;
2819                     assert(test ? mem.line == line : true); line = -1;
2820 
2821                     warningf("%d", __LINE__); line = __LINE__;
2822                     assert(testG ? mem.line == line : true); line = -1;
2823 
2824                     mem.warningf(cond, "%d", __LINE__); line = __LINE__;
2825                     assert(test ? mem.line == line : true); line = -1;
2826 
2827                     warningf(cond, "%d", __LINE__); line = __LINE__;
2828                     assert(testG ? mem.line == line : true); line = -1;
2829 
2830                     llVSgll = ll >= globalLogLevel;
2831                     lVSll = LogLevel.critical >= ll;
2832                     lVSgll = LogLevel.critical >= tll;
2833                     test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
2834                     testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
2835                         lVSgll && cond;
2836 
2837                     mem.critical(__LINE__); line = __LINE__;
2838                     assert(test ? mem.line == line : true); line = -1;
2839 
2840                     critical(__LINE__); line = __LINE__;
2841                     assert(testG ? mem.line == line : true); line = -1;
2842 
2843                     mem.critical(cond, __LINE__); line = __LINE__;
2844                     assert(test ? mem.line == line : true); line = -1;
2845 
2846                     critical(cond, __LINE__); line = __LINE__;
2847                     assert(testG ? mem.line == line : true); line = -1;
2848 
2849                     mem.criticalf("%d", __LINE__); line = __LINE__;
2850                     assert(test ? mem.line == line : true); line = -1;
2851 
2852                     criticalf("%d", __LINE__); line = __LINE__;
2853                     assert(testG ? mem.line == line : true); line = -1;
2854 
2855                     mem.criticalf(cond, "%d", __LINE__); line = __LINE__;
2856                     assert(test ? mem.line == line : true); line = -1;
2857 
2858                     criticalf(cond, "%d", __LINE__); line = __LINE__;
2859                     assert(testG ? mem.line == line : true); line = -1;
2860 
2861                     llVSgll = ll >= globalLogLevel;
2862                     lVSll = LogLevel.fatal >= ll;
2863                     lVSgll = LogLevel.fatal >= tll;
2864                     test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond;
2865                     testG = gllOff && llOff && tllOff && tllVSll && tllVSgll &&
2866                         lVSgll && cond;
2867 
2868                     mem.fatal(__LINE__); line = __LINE__;
2869                     assert(test ? mem.line == line : true); line = -1;
2870                     assert(test ? fatalLog : true);
2871                     fatalLog = false;
2872 
2873                     fatal(__LINE__); line = __LINE__;
2874                     assert(testG ? mem.line == line : true); line = -1;
2875                     assert(testG ? fatalLog : true);
2876                     fatalLog = false;
2877 
2878                     mem.fatal(cond, __LINE__); line = __LINE__;
2879                     assert(test ? mem.line == line : true); line = -1;
2880                     assert(test ? fatalLog : true);
2881                     fatalLog = false;
2882 
2883                     fatal(cond, __LINE__); line = __LINE__;
2884                     assert(testG ? mem.line == line : true); line = -1;
2885                     assert(testG ? fatalLog : true);
2886                     fatalLog = false;
2887 
2888                     mem.fatalf("%d", __LINE__); line = __LINE__;
2889                     assert(test ? mem.line == line : true); line = -1;
2890                     assert(test ? fatalLog : true);
2891                     fatalLog = false;
2892 
2893                     fatalf("%d", __LINE__); line = __LINE__;
2894                     assert(testG ? mem.line == line : true); line = -1;
2895                     assert(testG ? fatalLog : true);
2896                     fatalLog = false;
2897 
2898                     mem.fatalf(cond, "%d", __LINE__); line = __LINE__;
2899                     assert(test ? mem.line == line : true); line = -1;
2900                     assert(test ? fatalLog : true);
2901                     fatalLog = false;
2902 
2903                     fatalf(cond, "%d", __LINE__); line = __LINE__;
2904                     assert(testG ? mem.line == line : true); line = -1;
2905                     assert(testG ? fatalLog : true);
2906                     fatalLog = false;
2907                 }
2908             }
2909         }
2910     }
2911 }
2912 
2913 // Issue #5
2914 @safe unittest
2915 {
2916     import std..string : indexOf;
2917 
2918     auto oldunspecificLogger = sharedLog;
2919 
2920     scope(exit)
2921     {
2922         sharedLog = oldunspecificLogger;
2923         globalLogLevel = LogLevel.all;
2924     }
2925 
2926     auto tl = new TestLogger(LogLevel.info);
2927     sharedLog = tl;
2928 
2929     trace("trace");
2930     assert(tl.msg.indexOf("trace") == -1);
2931 }
2932 
2933 // Issue #5
2934 @safe unittest
2935 {
2936     import std.experimental.logger.multilogger : MultiLogger;
2937     import std..string : indexOf;
2938 
2939     stdThreadLocalLog.logLevel = LogLevel.all;
2940 
2941     auto oldunspecificLogger = sharedLog;
2942 
2943     scope(exit)
2944     {
2945         sharedLog = oldunspecificLogger;
2946         globalLogLevel = LogLevel.all;
2947     }
2948 
2949     auto logger = new MultiLogger(LogLevel.error);
2950 
2951     auto tl = new TestLogger(LogLevel.info);
2952     logger.insertLogger("required", tl);
2953     sharedLog = logger;
2954 
2955     trace("trace");
2956     assert(tl.msg.indexOf("trace") == -1);
2957     info("info");
2958     assert(tl.msg.indexOf("info") == -1);
2959     error("error");
2960     assert(tl.msg.indexOf("error") == 0);
2961 }
2962 
2963 @system unittest
2964 {
2965     import std.exception : assertThrown;
2966     auto tl = new TestLogger();
2967     assertThrown!Throwable(tl.fatal("fatal"));
2968 }
2969 
2970 // log objects with non-safe toString
2971 @system unittest
2972 {
2973     struct Test
2974     {
2975         string toString() const @system
2976         {
2977             return "test";
2978         }
2979     }
2980 
2981     auto tl = new TestLogger();
2982     tl.info(Test.init);
2983     assert(tl.msg == "test");
2984 }
2985 
2986 // Workaround for atomics not allowed in @safe code
2987 private auto trustedLoad(T)(ref shared T value) @trusted
2988 {
2989     import core.atomic : atomicLoad, MemoryOrder;
2990     return atomicLoad!(MemoryOrder.acq)(value);
2991 }
2992 
2993 // ditto
2994 private void trustedStore(T)(ref shared T dst, ref T src) @trusted
2995 {
2996     import core.atomic : atomicStore, MemoryOrder;
2997     atomicStore!(MemoryOrder.rel)(dst, src);
2998 }
2999 
3000 // check that thread-local logging does not propagate
3001 // to shared logger
3002 @system unittest
3003 {
3004     import core.atomic, core.thread, std.concurrency;
3005 
3006     static shared logged_count = 0;
3007 
3008     class TestLog : Logger
3009     {
3010         Tid tid;
3011 
3012         this()
3013         {
3014             super (LogLevel.trace);
3015             this.tid = thisTid;
3016         }
3017 
3018         override void writeLogMsg(ref LogEntry payload) @trusted
3019         {
3020             assert(thisTid == this.tid);
3021             atomicOp!"+="(logged_count, 1);
3022         }
3023     }
3024 
3025     class IgnoredLog : Logger
3026     {
3027         this()
3028         {
3029             super (LogLevel.trace);
3030         }
3031 
3032         override void writeLogMsg(ref LogEntry payload) @trusted
3033         {
3034             assert(false);
3035         }
3036     }
3037 
3038     auto oldSharedLog = sharedLog;
3039     scope(exit)
3040     {
3041         sharedLog = oldSharedLog;
3042     }
3043 
3044     sharedLog = new IgnoredLog;
3045     Thread[] spawned;
3046 
3047     foreach (i; 0 .. 4)
3048     {
3049         spawned ~= new Thread({
3050             stdThreadLocalLog = new TestLog;
3051             trace("zzzzzzzzzz");
3052         });
3053         spawned[$-1].start();
3054     }
3055 
3056     foreach (t; spawned)
3057         t.join();
3058 
3059     assert(atomicOp!"=="(logged_count, 4));
3060 }
3061 
3062 @safe unittest
3063 {
3064     auto dl = cast(FileLogger) sharedLog;
3065     assert(dl !is null);
3066     assert(dl.logLevel == LogLevel.all);
3067     assert(globalLogLevel == LogLevel.all);
3068 
3069     auto tl = cast(StdForwardLogger) stdThreadLocalLog;
3070     assert(tl !is null);
3071     stdThreadLocalLog.logLevel = LogLevel.all;
3072 }
3073 
3074 // https://issues.dlang.org/show_bug.cgi?id=14940
3075 @safe unittest
3076 {
3077     import std.typecons : Nullable;
3078 
3079     Nullable!int a = 1;
3080     auto l = new TestLogger();
3081     l.infof("log: %s", a);
3082     assert(l.msg == "log: 1");
3083 }
3084 
3085 // Ensure @system toString methods work
3086 @system unittest
3087 {
3088     enum SystemToStringMsg = "SystemToString";
3089     static struct SystemToString
3090     {
3091         string toString() @system
3092         {
3093             return SystemToStringMsg;
3094         }
3095     }
3096 
3097     auto tl = new TestLogger();
3098 
3099     SystemToString sts;
3100     tl.logf("%s", sts);
3101     assert(tl.msg == SystemToStringMsg);
3102 }
3103 
3104 // https://issues.dlang.org/show_bug.cgi?id=17328
3105 @safe unittest
3106 {
3107     import std.format : format;
3108 
3109     ubyte[] data = [0];
3110     string s = format("%(%02x%)", data); // format 00
3111     assert(s == "00");
3112 
3113     auto tl = new TestLogger();
3114 
3115     tl.infof("%(%02x%)", data);    // infof    000
3116 
3117     size_t i;
3118     string fs = tl.msg;
3119     for (; i < s.length; ++i)
3120     {
3121         assert(s[s.length - 1 - i] == fs[fs.length - 1 - i], fs);
3122     }
3123     assert(fs.length == 2);
3124 }
3125 
3126 // https://issues.dlang.org/show_bug.cgi?id=15954
3127 @safe unittest
3128 {
3129     import std.conv : to;
3130     auto tl = new TestLogger();
3131     tl.log("123456789".to!wstring);
3132     assert(tl.msg == "123456789");
3133 }
3134 
3135 // https://issues.dlang.org/show_bug.cgi?id=16256
3136 @safe unittest
3137 {
3138     import std.conv : to;
3139     auto tl = new TestLogger();
3140     tl.log("123456789"d);
3141     assert(tl.msg == "123456789");
3142 }
3143 
3144 // https://issues.dlang.org/show_bug.cgi?id=15517
3145 @system unittest
3146 {
3147     import std.file : exists, remove, tempDir;
3148     import std.path : buildPath;
3149     import std.stdio : File;
3150     import std..string : indexOf;
3151 
3152     string fn = tempDir.buildPath("logfile.log");
3153     if (exists(fn))
3154     {
3155         remove(fn);
3156     }
3157 
3158     auto oldShared = sharedLog;
3159     scope(exit)
3160     {
3161         sharedLog = oldShared;
3162         if (exists(fn))
3163         {
3164             remove(fn);
3165         }
3166     }
3167 
3168     auto ts = [ "Test log 1", "Test log 2", "Test log 3"];
3169 
3170     auto fl = new FileLogger(fn);
3171     sharedLog = fl;
3172     assert(exists(fn));
3173 
3174     foreach (t; ts)
3175     {
3176         log(t);
3177     }
3178 
3179     auto f = File(fn);
3180     auto l = f.byLine();
3181     assert(!l.empty);
3182     size_t idx;
3183     foreach (it; l)
3184     {
3185         assert(it.indexOf(ts[idx]) != -1, it);
3186         ++idx;
3187     }
3188 
3189     assert(exists(fn));
3190     fl.file.close();
3191 }
Suggestion Box / Bug Report