1 /*******************************************************************************
2 
3     Logging system for applications
4 
5     The most common pattern for using this class is to have a module-global
6     object and initialize it on construction:
7 
8     ---
9     module superapp.main;
10 
11     import ocean.util.log.Logger;
12 
13     private Logger log;
14     static this ()
15     {
16         log = Log.lookup("superapp.main");
17     }
18 
19     void main (string[] args)
20     {
21         log.info("App started with {} arguments: {}", args.length, args);
22     }
23     ---
24 
25     This usage can however be problematic in complex cases, as it introduces
26     a module constructor which can lead to cycles during module construction,
27     hence sometimes it might be worth moving to a narrower scope
28     (e.g. nesting it inside a class / struct).
29 
30     `Logger`s can be configured to output their message using a certain `Layout`
31     (see `ocean.util.log.layout` package), which will define how the message
32     looks like, and they can output to one or more `Appender`, which defines
33     where the message is writen. Common choices are standard outputs
34     (stdout / stderr), syslog, or a file.
35 
36     In order to make `Logger` common use cases more simple, and allow flexible
37     usage of logger without needing to re-compile the application, a module
38     to configure a hierarchy from a configuration file is available under
39     `ocean.util.log.Config`.
40 
41     Copyright:
42         Copyright (c) 2009-2017 dunnhumby Germany GmbH.
43         All rights reserved.
44 
45     License:
46         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
47         Alternatively, this file may be distributed under the terms of the Tango
48         3-Clause BSD License (see LICENSE_BSD.txt for details).
49 
50 *******************************************************************************/
51 
52 module ocean.util.log.Logger;
53 
54 import ocean.meta.types.Qualifiers;
55 import ocean.core.Verify;
56 import ocean.core.ExceptionDefinitions;
57 import ocean.io.model.IConduit;
58 import ocean.text.convert.Formatter;
59 import ocean.time.Clock;
60 import ocean.util.log.Appender;
61 import ocean.util.log.Event;
62 import ocean.util.log.Hierarchy;
63 import ocean.util.log.ILogger;
64 
65 version (unittest)
66 {
67     import ocean.core.Test;
68 }
69 
70 
71 /*******************************************************************************
72 
73     These represent the standard LOG4J event levels.
74 
75 *******************************************************************************/
76 
77 public alias ILogger.Level Level;
78 
79 
80 /*******************************************************************************
81 
82     Manager for routing Logger calls to the default hierarchy. Note
83     that you may have multiple hierarchies per application, but must
84     access the hierarchy directly for root() and lookup() methods within
85     each additional instance.
86 
87 *******************************************************************************/
88 
89 public struct Log
90 {
91     alias typeof(this) This;
92 
93     /***************************************************************************
94 
95         Structure for accumulating number of log events issued.
96 
97         Note:
98             this takes the logging level in account, so calls that are not
99             logged because of the minimum logging level are not counted.
100 
101     ***************************************************************************/
102 
103     public struct Stats
104     {
105         alias typeof(this) This;
106 
107         /// Number of debug log events issued
108         public uint logged_debug;
109 
110         /// Number of trace log events issued
111         public uint logged_trace;
112 
113         /// Number of verbose log events issued
114         public uint logged_verbose;
115 
116         /// Number of info log events issued
117         public uint logged_info;
118 
119         /// Number of warn log events issued
120         public uint logged_warn;
121 
122         /// Number of error log events issued
123         public uint logged_error;
124 
125         /// Number of fatal log events issue
126         public uint logged_fatal;
127 
128         static assert(Level.max == This.tupleof.length,
129                       "Number of members doesn't match Levels");
130 
131         /***********************************************************************
132 
133             Total count of all events emitted during this period.
134 
135             Returns:
136                 Total count of all events emitted during this period.
137 
138         ***********************************************************************/
139 
140         public uint total ()
141         {
142             uint total;
143 
144             foreach (field; this.tupleof)
145             {
146                 total += field;
147             }
148 
149             return total;
150         }
151 
152         /// Resets the counters
153         private void reset ()
154         {
155             foreach (ref field; this.tupleof)
156             {
157                 field = field.init;
158             }
159         }
160 
161         /***********************************************************************
162 
163             Accumulate the LogEvent into the stats.
164 
165             Params:
166                 event_level = level of the event that has been logged.
167 
168         ***********************************************************************/
169 
170         private void accumulate (Level event_level)
171         {
172             with (Level) switch (event_level)
173             {
174             case Debug:
175                 this.logged_debug++;
176                 break;
177             case Trace:
178                 this.logged_trace++;
179                 break;
180             case Verbose:
181                 this.logged_verbose++;
182                 break;
183             case Info:
184                 this.logged_info++;
185                 break;
186             case Warn:
187                 this.logged_warn++;
188                 break;
189             case Error:
190                 this.logged_error++;
191                 break;
192             case Fatal:
193                 this.logged_fatal++;
194                 break;
195             case None:
196                 break;
197             default:
198                 assert(false, "Non supported log level");
199             }
200         }
201     }
202 
203     /// Stores all the existing `Logger` in a hierarchical manner
204     private static HierarchyT!(Logger) hierarchy_;
205 
206     /// Logger stats
207     private static Stats logger_stats;
208 
209     /***************************************************************************
210 
211         Return the enum value associated with `name`, or a default value
212 
213         Params:
214             name = Case-independent string representation of an `ILogger.Level`
215                    If the name is not one of the logger, `def` is returned.
216             def  = Default value to return if no match is found for `name`
217 
218         Returns:
219             The `Level` value for `name`, or `def`
220 
221     ***************************************************************************/
222 
223     public static Level convert (cstring name, Level def = Level.Trace)
224     {
225         return ILogger.convert(name, def);
226     }
227 
228     /***************************************************************************
229 
230         Return the name associated with level
231 
232         Params:
233             level = The `Level` to get the name for
234 
235         Returns:
236             The name associated with `level`.
237 
238     ***************************************************************************/
239 
240     public static istring convert (Level level)
241     {
242         return ILogger.convert(level);
243     }
244 
245 
246     /***************************************************************************
247 
248         Return an instance of the named logger
249 
250         Names should be hierarchical in nature, using dot notation (with '.')
251         to separate each name section. For example, a typical name might be
252         something like "ocean.io.Stdout".
253 
254         If the logger does not currently exist, it is created and inserted into
255         the hierarchy. A parent will be attached to it, which will be either
256         the root logger or the closest ancestor in terms of the hierarchical
257         name space.
258 
259     ***************************************************************************/
260 
261     public static Logger lookup (cstring name)
262     {
263         return This.hierarchy().lookup(name);
264     }
265 
266     /***************************************************************************
267 
268         Return the root Logger instance.
269 
270         This is the ancestor of all loggers and, as such, can be used to
271         manipulate the entire hierarchy. For instance, setting the root 'level'
272         attribute will affect all other loggers in the tree.
273 
274     ***************************************************************************/
275 
276     public static Logger root ()
277     {
278         return This.hierarchy().root;
279     }
280 
281     /***************************************************************************
282 
283         Return (and potentially initialize) the hierarchy singleton
284 
285         Logger themselves have little knowledge about their hierarchy.
286         Everything is handled by a `HierarchyT!(Logger)` instance, which is
287         stored as a singleton in this `struct`, and for which convenient
288         functions are provided.
289         This function returns said singleton, and initialize it on first call.
290 
291     ***************************************************************************/
292 
293     public static HierarchyT!(Logger) hierarchy ()
294     {
295         if (This.hierarchy_ is null)
296         {
297             This.hierarchy_ = new HierarchyT!(Logger)("ocean");
298         }
299         return This.hierarchy_;
300     }
301 
302 
303     /***************************************************************************
304 
305         Initialize the behaviour of a basic logging hierarchy.
306 
307         Adds a StreamAppender to the root node, and sets the activity level
308         to be everything enabled.
309 
310     ***************************************************************************/
311 
312     public static void config (OutputStream stream, bool flush = true)
313     {
314         This.root.add(new AppendStream(stream, flush));
315     }
316 
317     /***************************************************************************
318 
319         Gets the stats of the logger system between two calls to this method.
320 
321         Returns:
322             number of log events issued after last call to stats, aggregated
323             per logger level
324 
325     ***************************************************************************/
326 
327     public static Stats stats ()
328     {
329         // Make a copy to return
330         Stats s = This.logger_stats;
331         This.logger_stats.reset();
332 
333         return s;
334     }
335 }
336 
337 
338 /*******************************************************************************
339 
340     Loggers are named entities, sometimes shared, sometimes specific to
341     a particular portion of code. The names are generally hierarchical in
342     nature, using dot notation (with '.') to separate each named section.
343     For example, a typical name might be something like "mail.send.writer"
344 
345     ---
346     import ocean.util.log.Logger;
347 
348     auto log = Log.lookup("mail.send.writer");
349 
350     log.info("an informational message");
351     log.error("an exception message: {}", exception.toString);
352 
353     // etc ...
354     ---
355 
356     It is considered good form to pass a logger instance as a function or
357     class-ctor argument, or to assign a new logger instance during static
358     class construction. For example: if it were considered appropriate to
359     have one logger instance per class, each might be constructed like so:
360     ---
361     module myapp.util.Transmogrifier;
362 
363     private Logger log;
364 
365     static this()
366     {
367         log = Log.lookup("myapp.util.Transmogrifier");
368     }
369     ---
370 
371     Messages passed to a Logger are assumed to be either self-contained
372     or configured with "{}" notation a la `ocean.text.convert.Formatter`:
373     ---
374     log.warn ("temperature is {} degrees!", 101);
375     ---
376 
377     Note that an internal workspace is used to format the message, which
378     is limited to 2048 bytes. Use "{.256}" truncation notation to limit
379     the size of individual message components. You can also use your own
380     formatting buffer:
381     ---
382     log.buffer(new char[](4096));
383 
384     log.warn("a very long warning: {}", someLongWarning);
385     ---
386 
387     Or you can use explicit formatting:
388     ---
389     char[4096] buf = void;
390 
391     log.warn(log.format(buf, "a very long warning: {}", someLongWarning));
392     ---
393 
394     If argument construction has some overhead which you'd like to avoid,
395     you can check to see whether a logger is active or not:
396 
397     ---
398     if (log.enabled(log.Warn))
399         log.warn("temperature is {} degrees!", complexFunction());
400     ---
401 
402     The `ocean.util.log` package closely follows both the API and the behaviour
403     as documented at the official Log4J site, where you'll find a good tutorial.
404     Those pages are hosted over:
405     http://logging.apache.org/log4j/docs/documentation.html
406 
407 *******************************************************************************/
408 
409 public final class Logger : ILogger
410 {
411     public alias Level.Info  Debug;     /// Shortcut to `Level` values
412     public alias Level.Trace Trace;     /// Ditto
413     public alias Level.Info  Verbose;   /// Ditto
414     public alias Level.Info  Info;      /// Ditto
415     public alias Level.Warn  Warn;      /// Ditto
416     public alias Level.Error Error;     /// Ditto
417     public alias Level.Fatal Fatal;     /// Ditto
418 
419     /// The hierarchy that host this logger (most likely Log.hierarchy).
420     private HierarchyT!(Logger) host_;
421     /// Next logger in the list, maintained by Hierarchy
422     package Logger          next;
423     /// Parent of this logger (maintained by Hierarchy)
424     package Logger          parent;
425     /// List of `Appender`s this Logger emits messages to
426     private Appender        appender_;
427     /// Name of this logger
428     private istring         name_;
429     /// Buffer to use for formatting. Can be `null`, see `buffer` properties
430     private mstring         buffer_;
431     /// `Level` at which this `Logger` is configured
432     package Level           level_;
433     /// When `true`, this `Logger` will use its ancestors `Appender`s as well
434     private bool            additive_;
435     /// Indicator if the log emits should be counted towards global stats.
436     package bool collect_stats;
437 
438     /***************************************************************************
439 
440         Construct a LoggerInstance with the specified name for the given
441         hierarchy. By default, logger instances are additive and are set
442         to emit all events.
443 
444         Params:
445             host = Hierarchy instance that is hosting this logger
446             name = name of this Logger
447 
448     ***************************************************************************/
449 
450     package this (HierarchyT!(Logger) host, istring name)
451     {
452         this.host_ = host;
453         this.level_ = Level.Trace;
454         this.additive_ = true;
455         this.collect_stats = true;
456         this.name_ = name;
457         this.buffer_ = new mstring(2048);
458     }
459 
460     /***************************************************************************
461 
462         Is this logger enabled for the specified Level?
463 
464         Params:
465             level = Level to check for.
466 
467         Returns:
468             `true` if `level` is `>=` to the current level of this `Logger`.
469 
470     ***************************************************************************/
471 
472     public bool enabled (Level level = Level.Fatal)
473     {
474         return this.host_.context.enabled(this.level_, level);
475     }
476 
477     /***************************************************************************
478 
479         Append a message with a severity of `Level.Debug`.
480 
481         Unlike other log level, this has to be abbreviated as `debug`
482         is a keyword.
483 
484         Params:
485             Args = Auto-deduced format string arguments
486             fmt = Format string to use.
487                   See `ocean.text.convert.Formatter` documentation for
488                   more informations.
489             args = Arguments to format according to `fmt`.
490 
491     ***************************************************************************/
492 
493     public void dbg (Args...) (cstring fmt, Args args)
494     {
495         this.format(Level.Debug, fmt, args);
496     }
497 
498     /***************************************************************************
499 
500         Append a message with a severity of `Level.Trace`.
501 
502         Params:
503             Args = Auto-deduced format string arguments
504             fmt = Format string to use.
505                   See `ocean.text.convert.Formatter` documentation for
506                   more informations.
507             args = Arguments to format according to `fmt`.
508 
509     ***************************************************************************/
510 
511     public void trace (Args...) (cstring fmt, Args args)
512     {
513         this.format(Level.Trace, fmt, args);
514     }
515 
516     /***************************************************************************
517 
518         Append a message with a severity of `Level.Verbose`.
519 
520         Params:
521             Args = Auto-deduced format string arguments
522             fmt = Format string to use.
523                   See `ocean.text.convert.Formatter` documentation for
524                   more informations.
525             args = Arguments to format according to `fmt`.
526 
527     ***************************************************************************/
528 
529     public void verbose (Args...) (cstring fmt, Args args)
530     {
531         this.format(Level.Debug, fmt, args);
532     }
533 
534     /***************************************************************************
535 
536         Append a message with a severity of `Level.Info`.
537 
538         Params:
539             Args = Auto-deduced format string arguments
540             fmt = Format string to use.
541                   See `ocean.text.convert.Formatter` documentation for
542                   more informations.
543             args = Arguments to format according to `fmt`.
544 
545     ***************************************************************************/
546 
547     public void info (Args...) (cstring fmt, Args args)
548     {
549         this.format(Level.Info, fmt, args);
550     }
551 
552     /***************************************************************************
553 
554         Append a message with a severity of `Level.Warn`.
555 
556         Params:
557             Args = Auto-deduced format string arguments
558             fmt = Format string to use.
559                   See `ocean.text.convert.Formatter` documentation for
560                   more informations.
561             args = Arguments to format according to `fmt`.
562 
563     ***************************************************************************/
564 
565     public void warn (Args...) (cstring fmt, Args args)
566     {
567         this.format(Level.Warn, fmt, args);
568     }
569 
570     /***************************************************************************
571 
572         Append a message with a severity of `Level.Error`.
573 
574         Params:
575             Args = Auto-deduced format string arguments
576             fmt = Format string to use.
577                   See `ocean.text.convert.Formatter` documentation for
578                   more informations.
579             args = Arguments to format according to `fmt`.
580 
581     ***************************************************************************/
582 
583     public void error (Args...) (cstring fmt, Args args)
584     {
585         this.format(Level.Error, fmt, args);
586     }
587 
588     /***************************************************************************
589 
590         Append a message with a severity of `Level.Fatal`.
591 
592         Params:
593             Args = Auto-deduced format string arguments
594             fmt = Format string to use.
595                   See `ocean.text.convert.Formatter` documentation for
596                   more informations.
597             args = Arguments to format according to `fmt`.
598 
599     ***************************************************************************/
600 
601     public void fatal (Args...) (cstring fmt, Args args)
602     {
603         this.format(Level.Fatal, fmt, args);
604     }
605 
606     /***************************************************************************
607 
608         Returns:
609             The name of this Logger (sans the appended dot).
610 
611     ***************************************************************************/
612 
613     public cstring name ()
614     {
615         auto i = this.name_.length;
616         if (i > 0)
617             --i;
618         return this.name_[0 .. i];
619     }
620 
621     /***************************************************************************
622 
623         Returns:
624             The Level this logger is set to
625 
626     ***************************************************************************/
627 
628     public Level level ()
629     {
630         return this.level_;
631     }
632 
633     /***************************************************************************
634 
635         Set the current level for this logger (and only this logger).
636 
637     ***************************************************************************/
638 
639     public Logger level (Level l)
640     {
641         return this.level(l, false);
642     }
643 
644     /***************************************************************************
645 
646         Set the current level for this logger,
647         and (optionally) all of its descendants.
648 
649         Params:
650             level = Level to set the Logger(s) to
651             propagate = If `true`, set the `level` to all of this `Logger`'s
652                         descendants as well.
653 
654         Returns:
655             `this`
656 
657     ***************************************************************************/
658 
659     public Logger level (Level level, bool propagate)
660     {
661         this.level_ = level;
662 
663         if (propagate)
664         {
665             this.host_.propagateValue!("level_")(this.name_, level);
666         }
667 
668         return this;
669     }
670 
671     /***************************************************************************
672 
673         Is this logger additive configured as additive ?
674 
675         Additive loggers should walk ancestors looking for more appenders.
676 
677     ***************************************************************************/
678 
679     public bool additive ()
680     {
681         return this.additive_;
682     }
683 
684     /***************************************************************************
685 
686         Set the additive status of this logger. See bool additive().
687 
688         Params:
689             enabled = new value for the additive property.
690 
691         Returns:
692             `this`
693 
694     ***************************************************************************/
695 
696     public Logger additive (bool enabled)
697     {
698         this.additive_ = enabled;
699         return this;
700     }
701 
702     /***************************************************************************
703 
704         Add (another) appender to this logger.
705 
706         Appenders are each invoked for log events as they are produced.
707         At most, one instance of each appender will be invoked.
708 
709         Params:
710             another = Appender to add to this logger. Mustn't be `null`.
711 
712         Returns:
713             `this`
714 
715     ***************************************************************************/
716 
717     public Logger add (Appender another)
718     {
719         verify(another !is null);
720         another.next = appender_;
721         this.appender_ = another;
722         return this;
723     }
724 
725     /***************************************************************************
726 
727         Remove all appenders from this Logger
728 
729         Returns:
730             `this`
731 
732     ***************************************************************************/
733 
734     public Logger clear ()
735     {
736         this.appender_ = null;
737         return this;
738     }
739 
740     /***************************************************************************
741 
742         Returns:
743             The current formatting buffer (null if none).
744 
745     ***************************************************************************/
746 
747     public mstring buffer ()
748     {
749         return this.buffer_;
750     }
751 
752     /***************************************************************************
753 
754         Set the current formatting buffer.
755 
756         The size of the internal buffer determines the length of the string that
757         can be logged.
758         To avoid GC allocations during logging, and excessive memory allocation
759         when user types are logged, the buffer is never resized internally.
760 
761         Params:
762             buf = Buffer to use. If `null` is used, nothing will be logged.
763 
764         Returns:
765             `this`
766 
767     ***************************************************************************/
768 
769     public Logger buffer (mstring buf)
770     {
771         buffer_ = buf;
772         return this;
773     }
774 
775     /***************************************************************************
776 
777         Toggles the stats collecting for this logger and optionally
778         for all its descendants.
779 
780         Params:
781             value = indicator if the stats collection for this logger should
782                     happen
783             propagate = should we propagate this change to all children
784                         loggers
785 
786     ***************************************************************************/
787 
788     public void collectStats (bool value, bool propagate)
789     {
790         this.collect_stats = value;
791 
792         if (propagate)
793         {
794             this.host_.propagateValue!("collect_stats")(this.name_, value);
795         }
796     }
797 
798     /***************************************************************************
799 
800         Returns:
801             Time since the program start
802 
803     ***************************************************************************/
804 
805     public TimeSpan runtime ()
806     {
807         return Clock.now - Clock.startTime();
808     }
809 
810     /***************************************************************************
811 
812         Emit a textual log message from the given string
813 
814         Params:
815             level = Message severity
816             exp   = Lazily evaluated string.
817                     If the provided `level` is not enabled, `exp` won't be
818                     evaluated at all.
819 
820         Returns:
821             `this`
822 
823     ***************************************************************************/
824 
825     public Logger append (Level level, lazy cstring exp)
826     {
827         if (host_.context.enabled (level_, level))
828         {
829             LogEvent event;
830 
831             // set the event attributes and append it
832             event.set(host_, level, exp, name.length ? name_[0..$-1] : "root");
833             this.append(event);
834         }
835         return this;
836     }
837 
838     /// Ditto
839     public alias append opCall;
840 
841     /***************************************************************************
842 
843         Emit a log message
844 
845         Implementation part of the public-ly available `append` function.
846         This walks through appenders and emit the message for each `Appender`
847         it can be emitted for.
848 
849         Params:
850             event = a `LogEvent` containing informations about the message
851                     to emit.
852 
853     ***************************************************************************/
854 
855     private void append (LogEvent event)
856     {
857         // indicator if the event was at least once emitted to the
858         // appender (to use for global stats)
859         bool event_emitted;
860 
861         // combine appenders from all ancestors
862         auto links = this;
863         Appender.Mask masks = 0;
864         do {
865             auto appender = links.appender_;
866 
867             // this level have an appender?
868             while (appender)
869             {
870                 auto mask = appender.mask;
871 
872                 // have we visited this appender already?
873                 if ((masks & mask) is 0)
874                     // is appender enabled for this level?
875                     if (appender.level <= event.level)
876                     {
877                         // append message and update mask
878                         appender.append(event);
879                         masks |= mask;
880                         event_emitted = true;
881                     }
882                 // process all appenders for this node
883                 appender = appender.next;
884             }
885             // process all ancestors
886         } while (links.additive_ && ((links = links.parent) !is null));
887 
888         // If the event was emitted to at least one appender, and the
889         // collecting stats for this log is enabled, increment the
890         // stats counters
891         if (this.collect_stats && event_emitted)
892         {
893             Log.logger_stats.accumulate(event.level);
894         }
895     }
896 
897     /***************************************************************************
898 
899         Format and emit a textual log message from the given arguments
900 
901         The formatted string emitted will have a length up to `buffer.length`,
902         which is 2048 by default.
903         If no formatting argument is provided (the call has only 2 parameters,
904         e.g. `format(Level.Trace, "Baguette");`), then the string will be just
905         emitted to the appender(s) verbatim and won't be limited in length.
906 
907         Params:
908             Args  = Auto-deduced argument list
909             level = Message severity
910             fmt   = Format string to use, see `ocean.text.convert.Formatter`
911             args  = Arguments to format according to `fmt`.
912 
913     ***************************************************************************/
914 
915     public void format (Args...) (Level level, cstring fmt, Args args)
916     {
917         static if (Args.length == 0)
918             this.append(level, fmt);
919         else
920         {
921             // If the buffer has length 0 / is null, we just don't log anything
922             if (this.buffer_.length)
923                 this.append(level, snformat(this.buffer_, fmt, args));
924         }
925     }
926 
927     /***************************************************************************
928 
929         See if the provided Logger name is a parent of this one.
930 
931         Note that each Logger name has a '.' appended to the end, such that
932         name segments will not partially match.
933 
934     ***************************************************************************/
935 
936     package bool isChildOf (istring candidate)
937     {
938         auto len = candidate.length;
939 
940         // possible parent if length is shorter
941         if (len < this.name_.length)
942             // does the prefix match? Note we append a "." to each
943             // (the root is a parent of everything)
944             return (len == 0 || candidate == this.name_[0 .. len]);
945         return false;
946     }
947 
948     /***************************************************************************
949 
950         See if the provided `Logger` is a better match as a parent of this one.
951 
952         This is used to restructure the hierarchy when a new logger instance
953         is introduced
954 
955     ***************************************************************************/
956 
957     package bool isCloserAncestor (Logger other)
958     {
959         auto name = other.name_;
960         if (this.isChildOf(name))
961             // is this a better (longer) match than prior parent?
962             if ((this.parent is null)
963                 || (name.length >= this.parent.name_.length))
964                 return true;
965         return false;
966     }
967 }
968 
969 
970 // Instantiation test for templated functions
971 unittest
972 {
973     static void test ()
974     {
975         Logger log = Log.lookup("ocean.util.log.Logger");
976         log.dbg("Souvent, pour s'amuser, les hommes d'équipage");
977         log.trace("Prennent des albatros, vastes oiseaux des mers,");
978         log.verbose("Qui suivent, indolents compagnons de voyage,");
979         log.info("Le navire glissant sur les gouffres amers.");
980 
981         log.warn("Le Poète est semblable au prince des nuées");
982         log.error("Qui hante la tempête et se rit de l'archer");
983         log.fatal("Exilé sur le sol au milieu des huées,");
984         log.format(Level.Fatal, "Ses ailes de géant l'empêchent de marcher.");
985     }
986 }
987 
988 unittest
989 {
990     test!("==")(Log.convert("info"), Level.Info);
991     test!("==")(Log.convert("Info"), Level.Info);
992     test!("==")(Log.convert("INFO"), Level.Info);
993     test!("==")(Log.convert("FATAL"), Level.Fatal);
994     // Use the default value
995     test!("==")(Log.convert("Info!"), Level.Trace);
996     test!("==")(Log.convert("Baguette", Level.Warn), Level.Warn);
997     // The first entry in the array
998     test!("==")(Log.convert("trace", Level.Error), Level.Trace);
999 }
1000 
1001 // Test that argumentless format call does not shrink the output
1002 unittest
1003 {
1004     static class Buffer : Appender
1005     {
1006         public struct Event { Logger.Level level; cstring message; }
1007         public Event[] result;
1008 
1009         public override Mask mask () { Mask m = 42; return m; }
1010         public override cstring name () { return "BufferAppender"; }
1011         public override void append (LogEvent e)
1012         {
1013             this.result ~= Event(e.level, e.toString());
1014         }
1015     }
1016 
1017     // Test string of 87 chars
1018     static immutable TestStr = "Ce qui se conçoit bien s'énonce clairement - Et les mots pour le dire arrivent aisément";
1019     scope appender = new Buffer();
1020     char[32] log_buffer;
1021     Logger log = (new Logger(Log.hierarchy(), "dummy"))
1022         .additive(false).add(appender).buffer(log_buffer);
1023     log.info("{}", TestStr);
1024     log.error(TestStr);
1025     test!("==")(appender.result.length, 2);
1026     // Trimmed string
1027     test!("==")(appender.result[0].level, Logger.Level.Info);
1028     test!("==")(appender.result[0].message, TestStr[0 .. 32]);
1029     // Full string
1030     test!("==")(appender.result[1].level, Logger.Level.Error);
1031     test!("==")(appender.result[1].message, TestStr);
1032 }
1033 
1034 // Test that the logger does not allocate if the Appender does not
1035 unittest
1036 {
1037     static class StaticBuffer : Appender
1038     {
1039         public struct Event { Logger.Level level; cstring message; char[128] buffer; }
1040 
1041         private Event[6] buffers;
1042         private size_t index;
1043 
1044         public override Mask mask () { Mask m = 42; return m; }
1045         public override cstring name () { return "StaticBufferAppender"; }
1046         public override void append (LogEvent e)
1047         {
1048             assert(this.index < this.buffers.length);
1049             auto str =snformat(this.buffers[this.index].buffer, "{}", e.toString());
1050             this.buffers[this.index].message = str;
1051             this.buffers[this.index++].level = e.level;
1052         }
1053     }
1054 
1055     scope appender = new StaticBuffer;
1056     Logger log = (new Logger(Log.hierarchy(), "dummy"))
1057         .additive(false)
1058         .add(appender);
1059 
1060     testNoAlloc({
1061             scope obj = new Object;
1062             log.trace("If you can keep your head when all about you, {}",
1063                       "Are losing theirs and blaming it on you;");
1064             log.info("If you can trust yourself when all men doubt you,");
1065             log.warn("But make {} for their {} too;", "allowance", "doubting");
1066             log.error("You'll have to google for the rest I'm afraid");
1067             log.fatal("{} - {} - {} - {}",
1068                       "This is some arg fmt", 42, obj, 1337.0f);
1069             log.format(Logger.Level.Info, "Just some {}", "more allocation tests");
1070     }());
1071 
1072     test!("==")(appender.buffers[0].level, Logger.Level.Trace);
1073     test!("==")(appender.buffers[1].level, Logger.Level.Info);
1074     test!("==")(appender.buffers[2].level, Logger.Level.Warn);
1075     test!("==")(appender.buffers[3].level, Logger.Level.Error);
1076     test!("==")(appender.buffers[4].level, Logger.Level.Fatal);
1077     test!("==")(appender.buffers[5].level, Logger.Level.Info);
1078 
1079     test!("==")(appender.buffers[0].message,
1080                 "If you can keep your head when all about you, " ~
1081                 "Are losing theirs and blaming it on you;");
1082     test!("==")(appender.buffers[1].message,
1083                 "If you can trust yourself when all men doubt you,");
1084     test!("==")(appender.buffers[2].message,
1085                 "But make allowance for their doubting too;");
1086     test!("==")(appender.buffers[3].message,
1087                 "You'll have to google for the rest I'm afraid");
1088     test!("==")(appender.buffers[4].message,
1089                 "This is some arg fmt - 42 - object.Object - 1337.00");
1090     test!("==")(appender.buffers[5].message, "Just some more allocation tests");
1091 }
1092 
1093 unittest
1094 {
1095     Logger log = (new Logger(Log.hierarchy(), "dummy")).additive(false);
1096     test!(">=")(log.runtime(), TimeSpan.fromNanos(0));
1097 }