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