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