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