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 }