1 // Written in the D programming language. 2 /** 3 Source: $(PHOBOSSRC std/experimental/logger/core.d) 4 */ 5 module std.experimental.logger.core; 6 7 import core.sync.mutex : Mutex; 8 import std.datetime.date : DateTime; 9 import std.datetime.systime : Clock, SysTime; 10 import std.range.primitives; 11 import std.traits; 12 13 import std.experimental.logger.filelogger; 14 15 /** This template evaluates if the passed `LogLevel` is active. 16 The previously described version statements are used to decide if the 17 `LogLevel` is active. The version statements only influence the compile 18 unit they are used with, therefore this function can only disable logging this 19 specific compile unit. 20 */ 21 template 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. 63 enum isLoggingActive = isLoggingActiveAt!(LogLevel.all); 64 65 /** This functions is used at runtime to determine if a `LogLevel` is 66 active. The same previously defined version statements are used to disable 67 certain levels. Again the version statements are associated with a compile 68 unit and can therefore not disable logging in other compile units. 69 pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc 70 */ 71 bool 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 103 LogLevel) defined in a user defined module where the filename has the 104 suffix "_loggerconfig.d". This `LogLevel` sets the minimal `LogLevel` 105 of the module. 106 107 A minimal `LogLevel` can be defined on a per module basis. 108 In order to define a module `LogLevel` a file with a modulename 109 "MODULENAME_loggerconfig" must be found. If no such module exists and the 110 module is a nested module, it is checked if there exists a 111 "PARENT_MODULE_loggerconfig" module with such a symbol. 112 If this module exists and it contains a `LogLevel` called logLevel this $(D 113 LogLevel) will be used. This parent lookup is continued until there is no 114 parent module. Then the moduleLogLevel is `LogLevel.all`. 115 */ 116 template moduleLogLevel(string moduleName) 117 if (!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 130 template moduleLogLevel(string moduleName) 131 if (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 155 private 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 164 The `SysTime` is formatted similar to 165 $(LREF std.datatime.DateTime.toISOExtString) except the fractional second part. 166 The fractional second part is in milliseconds and is always 3 digits. 167 */ 168 void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time) 169 if (isOutputRange!(OutputRange,string)) 170 { 171 import std.format : 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 183 In order for the data to be processed, the `LogLevel` of the log call must 184 be greater or equal to the `LogLevel` of the `sharedLog` and the 185 `defaultLogLevel`; additionally the condition passed must be `true`. 186 187 Params: 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 192 Example: 193 -------------------- 194 log(LogLevel.warning, true, "Hello World", 3.1415); 195 -------------------- 196 */ 197 void 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) 201 if (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 214 void 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 230 In order for the data to be processed the `LogLevel` of the log call must 231 be greater or equal to the `LogLevel` of the `sharedLog`. 232 233 Params: 234 ll = The `LogLevel` used by this log call. 235 args = The data that should be logged. 236 237 Example: 238 -------------------- 239 log(LogLevel.warning, "Hello World", 3.1415); 240 -------------------- 241 */ 242 void 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) 245 if (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 258 void 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 274 In order for the data to be processed the `LogLevel` of the 275 `sharedLog` must be greater or equal to the `defaultLogLevel` 276 add the condition passed must be `true`. 277 278 Params: 279 condition = The condition must be `true` for the data to be logged. 280 args = The data that should be logged. 281 282 Example: 283 -------------------- 284 log(true, "Hello World", 3.1415); 285 -------------------- 286 */ 287 void 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) 290 if (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 300 void 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 313 In order for the data to be processed the `LogLevel` of the 314 `sharedLog` must be greater or equal to the `defaultLogLevel`. 315 316 Params: 317 args = The data that should be logged. 318 319 Example: 320 -------------------- 321 log("Hello World", 3.1415); 322 -------------------- 323 */ 324 void log(int line = __LINE__, string file = __FILE__, 325 string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, 326 string moduleName = __MODULE__, A...)(lazy A args) 327 if ((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 338 void 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 351 In order for the data to be processed the `LogLevel` of the log call must 352 be greater or equal to the `LogLevel` of the `sharedLog` and the 353 `defaultLogLevel` additionally the condition passed must be `true`. 354 355 Params: 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 361 Example: 362 -------------------- 363 logf(LogLevel.warning, true, "Hello World %f", 3.1415); 364 -------------------- 365 */ 366 void 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 384 In order for the data to be processed the `LogLevel` of the log call must 385 be greater or equal to the `LogLevel` of the `sharedLog` and the 386 `defaultLogLevel`. 387 388 Params: 389 ll = The `LogLevel` used by this log call. 390 msg = The `printf`-style string. 391 args = The data that should be logged. 392 393 Example: 394 -------------------- 395 logf(LogLevel.warning, "Hello World %f", 3.1415); 396 -------------------- 397 */ 398 void 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 415 In order for the data to be processed the `LogLevel` of the log call must 416 be greater or equal to the `defaultLogLevel` additionally the condition 417 passed must be `true`. 418 419 Params: 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 424 Example: 425 -------------------- 426 logf(true, "Hello World %f", 3.1415); 427 -------------------- 428 */ 429 void 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 443 In order for the data to be processed the `LogLevel` of the log call must 444 be greater or equal to the `defaultLogLevel`. 445 446 Params: 447 msg = The `printf`-style string. 448 args = The data that should be logged. 449 450 Example: 451 -------------------- 452 logf("Hello World %f", 3.1415); 453 -------------------- 454 */ 455 void 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` 468 is encoded in the function name. 469 470 The aliases following this template create the public names of these log 471 functions. 472 */ 473 template 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 502 on a condition. 503 504 In order for the resulting log message to be logged the `LogLevel` must 505 be greater or equal than the `LogLevel` of the `stdThreadLocalLog` and 506 must be greater or equal than the global `LogLevel`. 507 Additionally the `LogLevel` must be greater or equal than the `LogLevel` 508 of the `stdSharedLogger`. 509 If a condition is given, it must evaluate to `true`. 510 511 Params: 512 condition = The condition must be `true` for the data to be logged. 513 args = The data that should be logged. 514 515 Example: 516 -------------------- 517 trace(1337, "is number"); 518 info(1337, "is number"); 519 error(1337, "is number"); 520 critical(1337, "is number"); 521 fatal(1337, "is number"); 522 trace(true, 1337, "is number"); 523 info(false, 1337, "is number"); 524 error(true, 1337, "is number"); 525 critical(false, 1337, "is number"); 526 fatal(true, 1337, "is number"); 527 -------------------- 528 */ 529 alias trace = defaultLogFunction!(LogLevel.trace); 530 /// Ditto 531 alias info = defaultLogFunction!(LogLevel.info); 532 /// Ditto 533 alias warning = defaultLogFunction!(LogLevel.warning); 534 /// Ditto 535 alias error = defaultLogFunction!(LogLevel.error); 536 /// Ditto 537 alias critical = defaultLogFunction!(LogLevel.critical); 538 /// Ditto 539 alias fatal = defaultLogFunction!(LogLevel.fatal); 540 541 /** This template provides the global `printf`-style log functions with 542 the `LogLevel` is encoded in the function name. 543 544 The aliases following this template create the public names of the log 545 functions. 546 */ 547 template 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 576 manner. 577 578 In order for the resulting log message to be logged the `LogLevel` must 579 be greater or equal than the `LogLevel` of the `sharedLog` and 580 must be greater or equal than the global `LogLevel`. 581 Additionally the `LogLevel` must be greater or equal than the `LogLevel` 582 of the `stdSharedLogger`. 583 584 Params: 585 msg = The `printf`-style string. 586 args = The data that should be logged. 587 588 Example: 589 -------------------- 590 tracef("is number %d", 1); 591 infof("is number %d", 2); 592 errorf("is number %d", 3); 593 criticalf("is number %d", 4); 594 fatalf("is number %d", 5); 595 -------------------- 596 597 The second version of the function logs data to the `sharedLog` in a $(D 598 printf)-style manner. 599 600 In order for the resulting log message to be logged the `LogLevel` must 601 be greater or equal than the `LogLevel` of the `sharedLog` and 602 must be greater or equal than the global `LogLevel`. 603 Additionally the `LogLevel` must be greater or equal than the `LogLevel` 604 of the `stdSharedLogger`. 605 606 Params: 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 611 Example: 612 -------------------- 613 tracef(false, "is number %d", 1); 614 infof(false, "is number %d", 2); 615 errorf(true, "is number %d", 3); 616 criticalf(true, "is number %d", 4); 617 fatalf(someFunct(), "is number %d", 5); 618 -------------------- 619 */ 620 alias tracef = defaultLogFunctionf!(LogLevel.trace); 621 /// Ditto 622 alias infof = defaultLogFunctionf!(LogLevel.info); 623 /// Ditto 624 alias warningf = defaultLogFunctionf!(LogLevel.warning); 625 /// Ditto 626 alias errorf = defaultLogFunctionf!(LogLevel.error); 627 /// Ditto 628 alias criticalf = defaultLogFunctionf!(LogLevel.critical); 629 /// Ditto 630 alias fatalf = defaultLogFunctionf!(LogLevel.fatal); 631 632 private 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 658 private void formatString(A...)(MsgRange oRange, A args) 659 { 660 import std.format : 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 /** 681 There 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). 683 If a log function with `LogLevel.fatal` is called the shutdown handler of 684 that logger is called. 685 */ 686 enum 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 704 logger a deriving class needs to implement the `writeLogMsg` method. By 705 default this is not thread-safe. 706 707 It is also possible to `override` the three methods `beginLogMsg`, 708 `logMsgPart` and `finishLogMsg` together, this option gives more 709 flexibility. 710 */ 711 abstract 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 : 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 : 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 : 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 : 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 : 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 : 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 1619 private __gshared Logger stdSharedDefaultLogger; 1620 private shared Logger stdSharedLogger; 1621 private 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 */ 1626 private @property Logger defaultSharedLoggerImpl() @trusted 1627 { 1628 import std.conv : 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.all); 1637 }()); 1638 1639 return stdSharedDefaultLogger; 1640 } 1641 1642 /** This property sets and gets the default `Logger`. 1643 1644 Example: 1645 ------------- 1646 sharedLog = new FileLogger(yourFile); 1647 ------------- 1648 The example sets a new `FileLogger` as new `sharedLog`. 1649 1650 If at some point you want to use the original default logger again, you can 1651 use $(D sharedLog = null;). This will put back the original. 1652 1653 Note: 1654 While getting and setting `sharedLog` is thread-safe, it has to be considered 1655 that the returned reference is only a current snapshot and in the following 1656 code, you must make sure no other thread reassigns to it between reading and 1657 writing `sharedLog`. 1658 1659 `sharedLog` is only thread-safe if the the used `Logger` is thread-safe. 1660 The default `Logger` is thread-safe. 1661 ------------- 1662 if (sharedLog !is myLogger) 1663 sharedLog = new myLogger; 1664 ------------- 1665 */ 1666 @property Logger sharedLog() @safe 1667 { 1668 static auto trustedLoad(ref shared Logger logger) @trusted 1669 { 1670 import core.atomic : atomicLoad, MemoryOrder; 1671 return cast() atomicLoad!(MemoryOrder.acq)(logger); 1672 //FIXME: Casting shared away here. Not good. See issue 16232. 1673 } 1674 1675 // If we have set up our own logger use that 1676 if (auto logger = trustedLoad(stdSharedLogger)) 1677 { 1678 return logger; 1679 } 1680 else 1681 { 1682 // Otherwise resort to the default logger 1683 return defaultSharedLoggerImpl; 1684 } 1685 } 1686 1687 /// Ditto 1688 @property void sharedLog(Logger logger) @trusted 1689 { 1690 import core.atomic : atomicStore, MemoryOrder; 1691 atomicStore!(MemoryOrder.rel)(stdSharedLogger, cast(shared) logger); 1692 } 1693 1694 /** This methods get and set the global `LogLevel`. 1695 1696 Every log message with a `LogLevel` lower as the global `LogLevel` 1697 will be discarded before it reaches `writeLogMessage` method of any 1698 `Logger`. 1699 */ 1700 /* Implementation note: 1701 For any public logging call, the global log level shall only be queried once on 1702 entry. Otherwise when another threads changes the level, we would work with 1703 different levels at different spots in the code. 1704 */ 1705 @property LogLevel globalLogLevel() @safe @nogc 1706 { 1707 return trustedLoad(stdLoggerGlobalLogLevel); 1708 } 1709 1710 /// Ditto 1711 @property void globalLogLevel(LogLevel ll) @safe 1712 { 1713 trustedStore(stdLoggerGlobalLogLevel, ll); 1714 } 1715 1716 // Thread Local 1717 1718 /** The `StdForwardLogger` will always forward anything to the sharedLog. 1719 1720 The `StdForwardLogger` will not throw if data is logged with $(D 1721 LogLevel.fatal). 1722 */ 1723 class StdForwardLogger : Logger 1724 { 1725 /** The default constructor for the `StdForwardLogger`. 1726 1727 Params: 1728 lv = The `LogLevel` for the `MultiLogger`. By default the $(D 1729 LogLevel) is `all`. 1730 */ 1731 this(const LogLevel lv = LogLevel.all) @safe 1732 { 1733 super(lv); 1734 this.fatalHandler = delegate() {}; 1735 } 1736 1737 override protected void writeLogMsg(ref LogEntry payload) 1738 { 1739 sharedLog.forwardMsg(payload); 1740 } 1741 } 1742 1743 /// 1744 @safe unittest 1745 { 1746 auto nl1 = new StdForwardLogger(LogLevel.all); 1747 } 1748 1749 /** This `LogLevel` is unqiue to every thread. 1750 1751 The thread local `Logger` will use this `LogLevel` to filter log calls 1752 every same way as presented earlier. 1753 */ 1754 //public LogLevel threadLogLevel = LogLevel.all; 1755 private Logger stdLoggerThreadLogger; 1756 private Logger stdLoggerDefaultThreadLogger; 1757 1758 /* This method returns the thread local default Logger. 1759 */ 1760 private @property Logger stdThreadLocalLogImpl() @trusted 1761 { 1762 import std.conv : emplace; 1763 1764 static void*[(__traits(classInstanceSize, StdForwardLogger) - 1) / (void*).sizeof + 1] _buffer; 1765 1766 auto buffer = cast(ubyte[]) _buffer; 1767 1768 if (stdLoggerDefaultThreadLogger is null) 1769 { 1770 stdLoggerDefaultThreadLogger = emplace!StdForwardLogger(buffer, LogLevel.all); 1771 } 1772 return stdLoggerDefaultThreadLogger; 1773 } 1774 1775 /** This function returns a thread unique `Logger`, that by default 1776 propergates all data logged to it to the `sharedLog`. 1777 1778 These properties can be used to set and get this `Logger`. Every 1779 modification to this `Logger` will only be visible in the thread the 1780 modification has been done from. 1781 1782 This `Logger` is called by the free standing log functions. This allows to 1783 create thread local redirections and still use the free standing log 1784 functions. 1785 */ 1786 @property Logger stdThreadLocalLog() @safe 1787 { 1788 // If we have set up our own logger use that 1789 if (auto logger = stdLoggerThreadLogger) 1790 return logger; 1791 else 1792 // Otherwise resort to the default logger 1793 return stdThreadLocalLogImpl; 1794 } 1795 1796 /// Ditto 1797 @property void stdThreadLocalLog(Logger logger) @safe 1798 { 1799 stdLoggerThreadLogger = logger; 1800 } 1801 1802 /// Ditto 1803 @system unittest 1804 { 1805 import std.experimental.logger.filelogger : FileLogger; 1806 import std.file : deleteme, remove; 1807 Logger l = stdThreadLocalLog; 1808 stdThreadLocalLog = new FileLogger(deleteme ~ "-someFile.log"); 1809 scope(exit) remove(deleteme ~ "-someFile.log"); 1810 1811 auto tempLog = stdThreadLocalLog; 1812 stdThreadLocalLog = l; 1813 destroy(tempLog); 1814 } 1815 1816 @safe unittest 1817 { 1818 LogLevel ll = globalLogLevel; 1819 globalLogLevel = LogLevel.fatal; 1820 assert(globalLogLevel == LogLevel.fatal); 1821 globalLogLevel = ll; 1822 } 1823 1824 package class TestLogger : Logger 1825 { 1826 int line = -1; 1827 string file = null; 1828 string func = null; 1829 string prettyFunc = null; 1830 string msg = null; 1831 LogLevel lvl; 1832 1833 this(const LogLevel lv = LogLevel.all) @safe 1834 { 1835 super(lv); 1836 } 1837 1838 override protected void writeLogMsg(ref LogEntry payload) @safe 1839 { 1840 this.line = payload.line; 1841 this.file = payload.file; 1842 this.func = payload.funcName; 1843 this.prettyFunc = payload.prettyFuncName; 1844 this.lvl = payload.logLevel; 1845 this.msg = payload.msg; 1846 } 1847 } 1848 1849 version (StdUnittest) private void testFuncNames(Logger logger) @safe 1850 { 1851 string s = "I'm here"; 1852 logger.log(s); 1853 } 1854 1855 @safe unittest 1856 { 1857 auto tl1 = new TestLogger(); 1858 testFuncNames(tl1); 1859 assert(tl1.func == "std.experimental.logger.core.testFuncNames", tl1.func); 1860 assert(tl1.prettyFunc == 1861 "void std.experimental.logger.core.testFuncNames(Logger logger) @safe", 1862 tl1.prettyFunc); 1863 assert(tl1.msg == "I'm here", tl1.msg); 1864 } 1865 1866 @safe unittest 1867 { 1868 auto tl1 = new TestLogger(LogLevel.all); 1869 tl1.log(); 1870 assert(tl1.line == __LINE__ - 1); 1871 tl1.log(true); 1872 assert(tl1.line == __LINE__ - 1); 1873 tl1.log(false); 1874 assert(tl1.line == __LINE__ - 3); 1875 tl1.log(LogLevel.info); 1876 assert(tl1.line == __LINE__ - 1); 1877 tl1.log(LogLevel.off); 1878 assert(tl1.line == __LINE__ - 3); 1879 tl1.log(LogLevel.info, true); 1880 assert(tl1.line == __LINE__ - 1); 1881 tl1.log(LogLevel.info, false); 1882 assert(tl1.line == __LINE__ - 3); 1883 1884 auto oldunspecificLogger = sharedLog; 1885 scope(exit) { 1886 sharedLog = oldunspecificLogger; 1887 } 1888 1889 sharedLog = tl1; 1890 1891 log(); 1892 assert(tl1.line == __LINE__ - 1); 1893 1894 log(LogLevel.info); 1895 assert(tl1.line == __LINE__ - 1); 1896 1897 log(true); 1898 assert(tl1.line == __LINE__ - 1); 1899 1900 log(LogLevel.warning, true); 1901 assert(tl1.line == __LINE__ - 1); 1902 1903 trace(); 1904 assert(tl1.line == __LINE__ - 1); 1905 } 1906 1907 @safe unittest 1908 { 1909 import std.experimental.logger.multilogger : MultiLogger; 1910 1911 auto tl1 = new TestLogger; 1912 auto tl2 = new TestLogger; 1913 1914 auto ml = new MultiLogger(); 1915 ml.insertLogger("one", tl1); 1916 ml.insertLogger("two", tl2); 1917 1918 string msg = "Hello Logger World"; 1919 ml.log(msg); 1920 int lineNumber = __LINE__ - 1; 1921 assert(tl1.msg == msg); 1922 assert(tl1.line == lineNumber); 1923 assert(tl2.msg == msg); 1924 assert(tl2.line == lineNumber); 1925 1926 ml.removeLogger("one"); 1927 ml.removeLogger("two"); 1928 auto n = ml.removeLogger("one"); 1929 assert(n is null); 1930 } 1931 1932 @safe unittest 1933 { 1934 bool errorThrown = false; 1935 auto tl = new TestLogger; 1936 auto dele = delegate() { 1937 errorThrown = true; 1938 }; 1939 tl.fatalHandler = dele; 1940 tl.fatal(); 1941 assert(errorThrown); 1942 } 1943 1944 @safe unittest 1945 { 1946 import std.conv : to; 1947 import std.exception : assertThrown, assertNotThrown; 1948 import std.format : format; 1949 1950 auto l = new TestLogger(LogLevel.all); 1951 string msg = "Hello Logger World"; 1952 l.log(msg); 1953 int lineNumber = __LINE__ - 1; 1954 assert(l.msg == msg); 1955 assert(l.line == lineNumber); 1956 assert(l.logLevel == LogLevel.all); 1957 1958 l.log(true, msg); 1959 lineNumber = __LINE__ - 1; 1960 assert(l.msg == msg, l.msg); 1961 assert(l.line == lineNumber); 1962 assert(l.logLevel == LogLevel.all); 1963 1964 l.log(false, msg); 1965 assert(l.msg == msg); 1966 assert(l.line == lineNumber, to!string(l.line)); 1967 assert(l.logLevel == LogLevel.all); 1968 1969 msg = "%s Another message"; 1970 l.logf(msg, "Yet"); 1971 lineNumber = __LINE__ - 1; 1972 assert(l.msg == msg.format("Yet")); 1973 assert(l.line == lineNumber); 1974 assert(l.logLevel == LogLevel.all); 1975 1976 l.logf(true, msg, "Yet"); 1977 lineNumber = __LINE__ - 1; 1978 assert(l.msg == msg.format("Yet")); 1979 assert(l.line == lineNumber); 1980 assert(l.logLevel == LogLevel.all); 1981 1982 l.logf(false, msg, "Yet"); 1983 assert(l.msg == msg.format("Yet")); 1984 assert(l.line == lineNumber); 1985 assert(l.logLevel == LogLevel.all); 1986 1987 () @trusted { 1988 assertThrown!Throwable(l.logf(LogLevel.fatal, msg, "Yet")); 1989 } (); 1990 lineNumber = __LINE__ - 2; 1991 assert(l.msg == msg.format("Yet")); 1992 assert(l.line == lineNumber); 1993 assert(l.logLevel == LogLevel.all); 1994 1995 () @trusted { 1996 assertThrown!Throwable(l.logf(LogLevel.fatal, true, msg, "Yet")); 1997 } (); 1998 lineNumber = __LINE__ - 2; 1999 assert(l.msg == msg.format("Yet")); 2000 assert(l.line == lineNumber); 2001 assert(l.logLevel == LogLevel.all); 2002 2003 assertNotThrown(l.logf(LogLevel.fatal, false, msg, "Yet")); 2004 assert(l.msg == msg.format("Yet")); 2005 assert(l.line == lineNumber); 2006 assert(l.logLevel == LogLevel.all); 2007 2008 auto oldunspecificLogger = sharedLog; 2009 2010 assert(oldunspecificLogger.logLevel == LogLevel.all, 2011 to!string(oldunspecificLogger.logLevel)); 2012 2013 assert(l.logLevel == LogLevel.all); 2014 sharedLog = l; 2015 assert(globalLogLevel == LogLevel.all, 2016 to!string(globalLogLevel)); 2017 2018 scope(exit) 2019 { 2020 sharedLog = oldunspecificLogger; 2021 } 2022 2023 assert(sharedLog.logLevel == LogLevel.all); 2024 assert(stdThreadLocalLog.logLevel == LogLevel.all); 2025 assert(globalLogLevel == LogLevel.all); 2026 2027 msg = "Another message"; 2028 log(msg); 2029 lineNumber = __LINE__ - 1; 2030 assert(l.logLevel == LogLevel.all); 2031 assert(l.line == lineNumber, to!string(l.line)); 2032 assert(l.msg == msg, l.msg); 2033 2034 log(true, msg); 2035 lineNumber = __LINE__ - 1; 2036 assert(l.msg == msg); 2037 assert(l.line == lineNumber); 2038 assert(l.logLevel == LogLevel.all); 2039 2040 log(false, msg); 2041 assert(l.msg == msg); 2042 assert(l.line == lineNumber); 2043 assert(l.logLevel == LogLevel.all); 2044 2045 msg = "%s Another message"; 2046 logf(msg, "Yet"); 2047 lineNumber = __LINE__ - 1; 2048 assert(l.msg == msg.format("Yet")); 2049 assert(l.line == lineNumber); 2050 assert(l.logLevel == LogLevel.all); 2051 2052 logf(true, msg, "Yet"); 2053 lineNumber = __LINE__ - 1; 2054 assert(l.msg == msg.format("Yet")); 2055 assert(l.line == lineNumber); 2056 assert(l.logLevel == LogLevel.all); 2057 2058 logf(false, msg, "Yet"); 2059 assert(l.msg == msg.format("Yet")); 2060 assert(l.line == lineNumber); 2061 assert(l.logLevel == LogLevel.all); 2062 2063 msg = "%s Another message"; 2064 () @trusted { 2065 assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet")); 2066 } (); 2067 lineNumber = __LINE__ - 2; 2068 assert(l.msg == msg.format("Yet")); 2069 assert(l.line == lineNumber); 2070 assert(l.logLevel == LogLevel.all); 2071 2072 () @trusted { 2073 assertThrown!Throwable(logf(LogLevel.fatal, true, msg, "Yet")); 2074 } (); 2075 lineNumber = __LINE__ - 2; 2076 assert(l.msg == msg.format("Yet")); 2077 assert(l.line == lineNumber); 2078 assert(l.logLevel == LogLevel.all); 2079 2080 assertNotThrown(logf(LogLevel.fatal, false, msg, "Yet")); 2081 assert(l.msg == msg.format("Yet")); 2082 assert(l.line == lineNumber); 2083 assert(l.logLevel == LogLevel.all); 2084 } 2085 2086 @system unittest // default logger 2087 { 2088 import std.file : deleteme, exists, remove; 2089 import std.stdio : File; 2090 import std..string : indexOf; 2091 2092 string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 2093 FileLogger l = new FileLogger(filename); 2094 auto oldunspecificLogger = sharedLog; 2095 sharedLog = l; 2096 2097 scope(exit) 2098 { 2099 remove(filename); 2100 assert(!exists(filename)); 2101 sharedLog = oldunspecificLogger; 2102 globalLogLevel = LogLevel.all; 2103 } 2104 2105 string notWritten = "this should not be written to file"; 2106 string written = "this should be written to file"; 2107 2108 globalLogLevel = LogLevel.critical; 2109 assert(globalLogLevel == LogLevel.critical); 2110 2111 log(LogLevel.warning, notWritten); 2112 log(LogLevel.critical, written); 2113 2114 l.file.flush(); 2115 l.file.close(); 2116 2117 auto file = File(filename, "r"); 2118 assert(!file.eof); 2119 2120 string readLine = file.readln(); 2121 assert(readLine.indexOf(written) != -1, readLine); 2122 assert(readLine.indexOf(notWritten) == -1, readLine); 2123 file.close(); 2124 } 2125 2126 @system unittest 2127 { 2128 import std.file : deleteme, remove; 2129 import std.stdio : File; 2130 import std..string : indexOf; 2131 2132 string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 2133 auto oldunspecificLogger = sharedLog; 2134 2135 scope(exit) 2136 { 2137 remove(filename); 2138 sharedLog = oldunspecificLogger; 2139 globalLogLevel = LogLevel.all; 2140 } 2141 2142 string notWritten = "this should not be written to file"; 2143 string written = "this should be written to file"; 2144 2145 auto l = new FileLogger(filename); 2146 sharedLog = l; 2147 sharedLog.logLevel = LogLevel.critical; 2148 2149 log(LogLevel.error, false, notWritten); 2150 log(LogLevel.critical, true, written); 2151 destroy(l); 2152 2153 auto file = File(filename, "r"); 2154 auto readLine = file.readln(); 2155 assert(!readLine.empty, readLine); 2156 assert(readLine.indexOf(written) != -1); 2157 assert(readLine.indexOf(notWritten) == -1); 2158 file.close(); 2159 } 2160 2161 @safe unittest 2162 { 2163 import std.conv : to; 2164 2165 auto tl = new TestLogger(LogLevel.all); 2166 int l = __LINE__; 2167 tl.info("a"); 2168 assert(tl.line == l+1); 2169 assert(tl.msg == "a"); 2170 assert(tl.logLevel == LogLevel.all); 2171 assert(globalLogLevel == LogLevel.all); 2172 l = __LINE__; 2173 tl.trace("b"); 2174 assert(tl.msg == "b", tl.msg); 2175 assert(tl.line == l+1, to!string(tl.line)); 2176 } 2177 2178 // testing possible log conditions 2179 @safe unittest 2180 { 2181 import std.conv : to; 2182 import std.format : format; 2183 import std..string : indexOf; 2184 2185 auto oldunspecificLogger = sharedLog; 2186 2187 auto mem = new TestLogger; 2188 mem.fatalHandler = delegate() {}; 2189 sharedLog = mem; 2190 2191 scope(exit) 2192 { 2193 sharedLog = oldunspecificLogger; 2194 globalLogLevel = LogLevel.all; 2195 } 2196 2197 int value = 0; 2198 foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2199 LogLevel.info, LogLevel.warning, LogLevel.error, 2200 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2201 { 2202 2203 globalLogLevel = gll; 2204 2205 foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2206 LogLevel.info, LogLevel.warning, LogLevel.error, 2207 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2208 { 2209 2210 mem.logLevel = ll; 2211 2212 foreach (cond; [true, false]) 2213 { 2214 foreach (condValue; [true, false]) 2215 { 2216 foreach (memOrG; [true, false]) 2217 { 2218 foreach (prntf; [true, false]) 2219 { 2220 foreach (ll2; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2221 LogLevel.info, LogLevel.warning, 2222 LogLevel.error, LogLevel.critical, 2223 LogLevel.fatal, LogLevel.off]) 2224 { 2225 foreach (singleMulti; 0 .. 2) 2226 { 2227 int lineCall; 2228 mem.msg = "-1"; 2229 if (memOrG) 2230 { 2231 if (prntf) 2232 { 2233 if (cond) 2234 { 2235 if (singleMulti == 0) 2236 { 2237 mem.logf(ll2, condValue, "%s", 2238 value); 2239 lineCall = __LINE__; 2240 } 2241 else 2242 { 2243 mem.logf(ll2, condValue, 2244 "%d %d", value, value); 2245 lineCall = __LINE__; 2246 } 2247 } 2248 else 2249 { 2250 if (singleMulti == 0) 2251 { 2252 mem.logf(ll2, "%s", value); 2253 lineCall = __LINE__; 2254 } 2255 else 2256 { 2257 mem.logf(ll2, "%d %d", 2258 value, value); 2259 lineCall = __LINE__; 2260 } 2261 } 2262 } 2263 else 2264 { 2265 if (cond) 2266 { 2267 if (singleMulti == 0) 2268 { 2269 mem.log(ll2, condValue, 2270 to!string(value)); 2271 lineCall = __LINE__; 2272 } 2273 else 2274 { 2275 mem.log(ll2, condValue, 2276 to!string(value), value); 2277 lineCall = __LINE__; 2278 } 2279 } 2280 else 2281 { 2282 if (singleMulti == 0) 2283 { 2284 mem.log(ll2, to!string(value)); 2285 lineCall = __LINE__; 2286 } 2287 else 2288 { 2289 mem.log(ll2, 2290 to!string(value), 2291 value); 2292 lineCall = __LINE__; 2293 } 2294 } 2295 } 2296 } 2297 else 2298 { 2299 if (prntf) 2300 { 2301 if (cond) 2302 { 2303 if (singleMulti == 0) 2304 { 2305 logf(ll2, condValue, "%s", 2306 value); 2307 lineCall = __LINE__; 2308 } 2309 else 2310 { 2311 logf(ll2, condValue, 2312 "%s %d", value, value); 2313 lineCall = __LINE__; 2314 } 2315 } 2316 else 2317 { 2318 if (singleMulti == 0) 2319 { 2320 logf(ll2, "%s", value); 2321 lineCall = __LINE__; 2322 } 2323 else 2324 { 2325 logf(ll2, "%s %s", value, 2326 value); 2327 lineCall = __LINE__; 2328 } 2329 } 2330 } 2331 else 2332 { 2333 if (cond) 2334 { 2335 if (singleMulti == 0) 2336 { 2337 log(ll2, condValue, 2338 to!string(value)); 2339 lineCall = __LINE__; 2340 } 2341 else 2342 { 2343 log(ll2, condValue, value, 2344 to!string(value)); 2345 lineCall = __LINE__; 2346 } 2347 } 2348 else 2349 { 2350 if (singleMulti == 0) 2351 { 2352 log(ll2, to!string(value)); 2353 lineCall = __LINE__; 2354 } 2355 else 2356 { 2357 log(ll2, value, 2358 to!string(value)); 2359 lineCall = __LINE__; 2360 } 2361 } 2362 } 2363 } 2364 2365 string valueStr = to!string(value); 2366 ++value; 2367 2368 bool ll2Off = (ll2 != LogLevel.off); 2369 bool gllOff = (gll != LogLevel.off); 2370 bool llOff = (ll != LogLevel.off); 2371 bool condFalse = (cond ? condValue : true); 2372 bool ll2VSgll = (ll2 >= gll); 2373 bool ll2VSll = (ll2 >= ll); 2374 2375 bool shouldLog = ll2Off && gllOff && llOff 2376 && condFalse && ll2VSgll && ll2VSll; 2377 2378 /* 2379 writefln( 2380 "go(%b) ll2o(%b) c(%b) lg(%b) ll(%b) s(%b)" 2381 , gll != LogLevel.off, ll2 != LogLevel.off, 2382 cond ? condValue : true, 2383 ll2 >= gll, ll2 >= ll, shouldLog); 2384 */ 2385 2386 2387 if (shouldLog) 2388 { 2389 assert(mem.msg.indexOf(valueStr) != -1, 2390 format( 2391 "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~ 2392 "cond(%b) condValue(%b)" ~ 2393 " memOrG(%b) shouldLog(%b) %s == %s" ~ 2394 " %b %b %b %b %b", 2395 lineCall, ll2Off, gll, ll, ll2, cond, 2396 condValue, memOrG, shouldLog, mem.msg, 2397 valueStr, gllOff, llOff, condFalse, 2398 ll2VSgll, ll2VSll 2399 )); 2400 } 2401 else 2402 { 2403 assert(mem.msg.indexOf(valueStr), 2404 format( 2405 "lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~ 2406 "cond(%b) condValue(%b)" ~ 2407 " memOrG(%b) shouldLog(%b) %s == %s" ~ 2408 " %b %b %b %b %b", 2409 lineCall, ll2Off, gll, ll, ll2, cond, 2410 condValue, memOrG, shouldLog, mem.msg, 2411 valueStr, gllOff, llOff, condFalse, 2412 ll2VSgll, ll2VSll 2413 )); 2414 } 2415 } 2416 } 2417 } 2418 } 2419 } 2420 } 2421 } 2422 } 2423 } 2424 2425 // more testing 2426 @safe unittest 2427 { 2428 import std.conv : to; 2429 import std.format : format; 2430 import std..string : indexOf; 2431 2432 auto oldunspecificLogger = sharedLog; 2433 2434 auto mem = new TestLogger; 2435 mem.fatalHandler = delegate() {}; 2436 sharedLog = mem; 2437 2438 scope(exit) 2439 { 2440 sharedLog = oldunspecificLogger; 2441 globalLogLevel = LogLevel.all; 2442 } 2443 2444 int value = 0; 2445 foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2446 LogLevel.info, LogLevel.warning, LogLevel.error, 2447 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2448 { 2449 2450 globalLogLevel = gll; 2451 2452 foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2453 LogLevel.info, LogLevel.warning, LogLevel.error, 2454 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2455 { 2456 mem.logLevel = ll; 2457 2458 foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2459 LogLevel.info, LogLevel.warning, LogLevel.error, 2460 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2461 { 2462 stdThreadLocalLog.logLevel = tll; 2463 2464 foreach (cond; [true, false]) 2465 { 2466 foreach (condValue; [true, false]) 2467 { 2468 foreach (memOrG; [true, false]) 2469 { 2470 foreach (prntf; [true, false]) 2471 { 2472 foreach (singleMulti; 0 .. 2) 2473 { 2474 int lineCall; 2475 mem.msg = "-1"; 2476 if (memOrG) 2477 { 2478 if (prntf) 2479 { 2480 if (cond) 2481 { 2482 if (singleMulti == 0) 2483 { 2484 mem.logf(condValue, "%s", 2485 value); 2486 lineCall = __LINE__; 2487 } 2488 else 2489 { 2490 mem.logf(condValue, 2491 "%d %d", value, value); 2492 lineCall = __LINE__; 2493 } 2494 } 2495 else 2496 { 2497 if (singleMulti == 0) 2498 { 2499 mem.logf("%s", value); 2500 lineCall = __LINE__; 2501 } 2502 else 2503 { 2504 mem.logf("%d %d", 2505 value, value); 2506 lineCall = __LINE__; 2507 } 2508 } 2509 } 2510 else 2511 { 2512 if (cond) 2513 { 2514 if (singleMulti == 0) 2515 { 2516 mem.log(condValue, 2517 to!string(value)); 2518 lineCall = __LINE__; 2519 } 2520 else 2521 { 2522 mem.log(condValue, 2523 to!string(value), value); 2524 lineCall = __LINE__; 2525 } 2526 } 2527 else 2528 { 2529 if (singleMulti == 0) 2530 { 2531 mem.log(to!string(value)); 2532 lineCall = __LINE__; 2533 } 2534 else 2535 { 2536 mem.log(to!string(value), 2537 value); 2538 lineCall = __LINE__; 2539 } 2540 } 2541 } 2542 } 2543 else 2544 { 2545 if (prntf) 2546 { 2547 if (cond) 2548 { 2549 if (singleMulti == 0) 2550 { 2551 logf(condValue, "%s", value); 2552 lineCall = __LINE__; 2553 } 2554 else 2555 { 2556 logf(condValue, "%s %d", value, 2557 value); 2558 lineCall = __LINE__; 2559 } 2560 } 2561 else 2562 { 2563 if (singleMulti == 0) 2564 { 2565 logf("%s", value); 2566 lineCall = __LINE__; 2567 } 2568 else 2569 { 2570 logf("%s %s", value, value); 2571 lineCall = __LINE__; 2572 } 2573 } 2574 } 2575 else 2576 { 2577 if (cond) 2578 { 2579 if (singleMulti == 0) 2580 { 2581 log(condValue, 2582 to!string(value)); 2583 lineCall = __LINE__; 2584 } 2585 else 2586 { 2587 log(condValue, value, 2588 to!string(value)); 2589 lineCall = __LINE__; 2590 } 2591 } 2592 else 2593 { 2594 if (singleMulti == 0) 2595 { 2596 log(to!string(value)); 2597 lineCall = __LINE__; 2598 } 2599 else 2600 { 2601 log(value, to!string(value)); 2602 lineCall = __LINE__; 2603 } 2604 } 2605 } 2606 } 2607 2608 string valueStr = to!string(value); 2609 ++value; 2610 2611 bool gllOff = (gll != LogLevel.off); 2612 bool llOff = (ll != LogLevel.off); 2613 bool tllOff = (tll != LogLevel.off); 2614 bool llVSgll = (ll >= gll); 2615 bool tllVSll = 2616 (stdThreadLocalLog.logLevel >= ll); 2617 bool condFalse = (cond ? condValue : true); 2618 2619 bool shouldLog = gllOff && llOff 2620 && (memOrG ? true : tllOff) 2621 && (memOrG ? 2622 (ll >= gll) : 2623 (tll >= gll && tll >= ll)) 2624 && condFalse; 2625 2626 if (shouldLog) 2627 { 2628 assert(mem.msg.indexOf(valueStr) != -1, 2629 format("\ngll(%s) ll(%s) tll(%s) " ~ 2630 "cond(%s) condValue(%s) " ~ 2631 "memOrG(%s) prntf(%s) " ~ 2632 "singleMulti(%s)", 2633 gll, ll, tll, cond, condValue, 2634 memOrG, prntf, singleMulti) 2635 ~ format(" gllOff(%s) llOff(%s) " ~ 2636 "llVSgll(%s) tllVSll(%s) " ~ 2637 "tllOff(%s) condFalse(%s) " 2638 ~ "shoudlLog(%s)", 2639 gll != LogLevel.off, 2640 ll != LogLevel.off, llVSgll, 2641 tllVSll, tllOff, condFalse, 2642 shouldLog) 2643 ~ format("msg(%s) line(%s) " ~ 2644 "lineCall(%s) valueStr(%s)", 2645 mem.msg, mem.line, lineCall, 2646 valueStr) 2647 ); 2648 } 2649 else 2650 { 2651 assert(mem.msg.indexOf(valueStr) == -1, 2652 format("\ngll(%s) ll(%s) tll(%s) " ~ 2653 "cond(%s) condValue(%s) " ~ 2654 "memOrG(%s) prntf(%s) " ~ 2655 "singleMulti(%s)", 2656 gll, ll, tll, cond, condValue, 2657 memOrG, prntf, singleMulti) 2658 ~ format(" gllOff(%s) llOff(%s) " ~ 2659 "llVSgll(%s) tllVSll(%s) " ~ 2660 "tllOff(%s) condFalse(%s) " 2661 ~ "shoudlLog(%s)", 2662 gll != LogLevel.off, 2663 ll != LogLevel.off, llVSgll, 2664 tllVSll, tllOff, condFalse, 2665 shouldLog) 2666 ~ format("msg(%s) line(%s) " ~ 2667 "lineCall(%s) valueStr(%s)", 2668 mem.msg, mem.line, lineCall, 2669 valueStr) 2670 ); 2671 } 2672 } 2673 } 2674 } 2675 } 2676 } 2677 } 2678 } 2679 } 2680 } 2681 2682 // testing more possible log conditions 2683 @safe unittest 2684 { 2685 bool fatalLog; 2686 auto mem = new TestLogger; 2687 mem.fatalHandler = delegate() { fatalLog = true; }; 2688 auto oldunspecificLogger = sharedLog; 2689 2690 stdThreadLocalLog.logLevel = LogLevel.all; 2691 2692 sharedLog = mem; 2693 scope(exit) 2694 { 2695 sharedLog = oldunspecificLogger; 2696 globalLogLevel = LogLevel.all; 2697 } 2698 2699 foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2700 LogLevel.info, LogLevel.warning, LogLevel.error, 2701 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2702 { 2703 2704 globalLogLevel = gll; 2705 2706 foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2707 LogLevel.info, LogLevel.warning, LogLevel.error, 2708 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2709 { 2710 mem.logLevel = ll; 2711 2712 foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, 2713 LogLevel.info, LogLevel.warning, LogLevel.error, 2714 LogLevel.critical, LogLevel.fatal, LogLevel.off]) 2715 { 2716 stdThreadLocalLog.logLevel = tll; 2717 2718 foreach (cond; [true, false]) 2719 { 2720 assert(globalLogLevel == gll); 2721 assert(mem.logLevel == ll); 2722 2723 bool gllVSll = LogLevel.trace >= globalLogLevel; 2724 bool llVSgll = ll >= globalLogLevel; 2725 bool lVSll = LogLevel.trace >= ll; 2726 bool gllOff = globalLogLevel != LogLevel.off; 2727 bool llOff = mem.logLevel != LogLevel.off; 2728 bool tllOff = stdThreadLocalLog.logLevel != LogLevel.off; 2729 bool tllVSll = tll >= ll; 2730 bool tllVSgll = tll >= gll; 2731 bool lVSgll = LogLevel.trace >= tll; 2732 2733 bool test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; 2734 bool testG = gllOff && llOff && tllOff && lVSgll && tllVSll && tllVSgll && cond; 2735 2736 mem.line = -1; 2737 /* 2738 writefln("gll(%3u) ll(%3u) cond(%b) test(%b)", 2739 gll, ll, cond, test); 2740 writefln("%b %b %b %b %b %b test2(%b)", llVSgll, gllVSll, lVSll, 2741 gllOff, llOff, cond, test2); 2742 */ 2743 2744 mem.trace(__LINE__); int line = __LINE__; 2745 assert(test ? mem.line == line : true); line = -1; 2746 2747 trace(__LINE__); line = __LINE__; 2748 assert(testG ? mem.line == line : true); line = -1; 2749 2750 mem.trace(cond, __LINE__); line = __LINE__; 2751 assert(test ? mem.line == line : true); line = -1; 2752 2753 trace(cond, __LINE__); line = __LINE__; 2754 assert(testG ? mem.line == line : true); line = -1; 2755 2756 mem.tracef("%d", __LINE__); line = __LINE__; 2757 assert(test ? mem.line == line : true); line = -1; 2758 2759 tracef("%d", __LINE__); line = __LINE__; 2760 assert(testG ? mem.line == line : true); line = -1; 2761 2762 mem.tracef(cond, "%d", __LINE__); line = __LINE__; 2763 assert(test ? mem.line == line : true); line = -1; 2764 2765 tracef(cond, "%d", __LINE__); line = __LINE__; 2766 assert(testG ? mem.line == line : true); line = -1; 2767 2768 llVSgll = ll >= globalLogLevel; 2769 lVSll = LogLevel.info >= ll; 2770 lVSgll = LogLevel.info >= tll; 2771 test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; 2772 testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && 2773 lVSgll && cond; 2774 2775 mem.info(__LINE__); line = __LINE__; 2776 assert(test ? mem.line == line : true); line = -1; 2777 2778 info(__LINE__); line = __LINE__; 2779 assert(testG ? mem.line == line : true); line = -1; 2780 2781 mem.info(cond, __LINE__); line = __LINE__; 2782 assert(test ? mem.line == line : true); line = -1; 2783 2784 info(cond, __LINE__); line = __LINE__; 2785 assert(testG ? mem.line == line : true); line = -1; 2786 2787 mem.infof("%d", __LINE__); line = __LINE__; 2788 assert(test ? mem.line == line : true); line = -1; 2789 2790 infof("%d", __LINE__); line = __LINE__; 2791 assert(testG ? mem.line == line : true); line = -1; 2792 2793 mem.infof(cond, "%d", __LINE__); line = __LINE__; 2794 assert(test ? mem.line == line : true); line = -1; 2795 2796 infof(cond, "%d", __LINE__); line = __LINE__; 2797 assert(testG ? mem.line == line : true); line = -1; 2798 2799 llVSgll = ll >= globalLogLevel; 2800 lVSll = LogLevel.warning >= ll; 2801 lVSgll = LogLevel.warning >= tll; 2802 test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; 2803 testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && 2804 lVSgll && cond; 2805 2806 mem.warning(__LINE__); line = __LINE__; 2807 assert(test ? mem.line == line : true); line = -1; 2808 2809 warning(__LINE__); line = __LINE__; 2810 assert(testG ? mem.line == line : true); line = -1; 2811 2812 mem.warning(cond, __LINE__); line = __LINE__; 2813 assert(test ? mem.line == line : true); line = -1; 2814 2815 warning(cond, __LINE__); line = __LINE__; 2816 assert(testG ? mem.line == line : true); line = -1; 2817 2818 mem.warningf("%d", __LINE__); line = __LINE__; 2819 assert(test ? mem.line == line : true); line = -1; 2820 2821 warningf("%d", __LINE__); line = __LINE__; 2822 assert(testG ? mem.line == line : true); line = -1; 2823 2824 mem.warningf(cond, "%d", __LINE__); line = __LINE__; 2825 assert(test ? mem.line == line : true); line = -1; 2826 2827 warningf(cond, "%d", __LINE__); line = __LINE__; 2828 assert(testG ? mem.line == line : true); line = -1; 2829 2830 llVSgll = ll >= globalLogLevel; 2831 lVSll = LogLevel.critical >= ll; 2832 lVSgll = LogLevel.critical >= tll; 2833 test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; 2834 testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && 2835 lVSgll && cond; 2836 2837 mem.critical(__LINE__); line = __LINE__; 2838 assert(test ? mem.line == line : true); line = -1; 2839 2840 critical(__LINE__); line = __LINE__; 2841 assert(testG ? mem.line == line : true); line = -1; 2842 2843 mem.critical(cond, __LINE__); line = __LINE__; 2844 assert(test ? mem.line == line : true); line = -1; 2845 2846 critical(cond, __LINE__); line = __LINE__; 2847 assert(testG ? mem.line == line : true); line = -1; 2848 2849 mem.criticalf("%d", __LINE__); line = __LINE__; 2850 assert(test ? mem.line == line : true); line = -1; 2851 2852 criticalf("%d", __LINE__); line = __LINE__; 2853 assert(testG ? mem.line == line : true); line = -1; 2854 2855 mem.criticalf(cond, "%d", __LINE__); line = __LINE__; 2856 assert(test ? mem.line == line : true); line = -1; 2857 2858 criticalf(cond, "%d", __LINE__); line = __LINE__; 2859 assert(testG ? mem.line == line : true); line = -1; 2860 2861 llVSgll = ll >= globalLogLevel; 2862 lVSll = LogLevel.fatal >= ll; 2863 lVSgll = LogLevel.fatal >= tll; 2864 test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; 2865 testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && 2866 lVSgll && cond; 2867 2868 mem.fatal(__LINE__); line = __LINE__; 2869 assert(test ? mem.line == line : true); line = -1; 2870 assert(test ? fatalLog : true); 2871 fatalLog = false; 2872 2873 fatal(__LINE__); line = __LINE__; 2874 assert(testG ? mem.line == line : true); line = -1; 2875 assert(testG ? fatalLog : true); 2876 fatalLog = false; 2877 2878 mem.fatal(cond, __LINE__); line = __LINE__; 2879 assert(test ? mem.line == line : true); line = -1; 2880 assert(test ? fatalLog : true); 2881 fatalLog = false; 2882 2883 fatal(cond, __LINE__); line = __LINE__; 2884 assert(testG ? mem.line == line : true); line = -1; 2885 assert(testG ? fatalLog : true); 2886 fatalLog = false; 2887 2888 mem.fatalf("%d", __LINE__); line = __LINE__; 2889 assert(test ? mem.line == line : true); line = -1; 2890 assert(test ? fatalLog : true); 2891 fatalLog = false; 2892 2893 fatalf("%d", __LINE__); line = __LINE__; 2894 assert(testG ? mem.line == line : true); line = -1; 2895 assert(testG ? fatalLog : true); 2896 fatalLog = false; 2897 2898 mem.fatalf(cond, "%d", __LINE__); line = __LINE__; 2899 assert(test ? mem.line == line : true); line = -1; 2900 assert(test ? fatalLog : true); 2901 fatalLog = false; 2902 2903 fatalf(cond, "%d", __LINE__); line = __LINE__; 2904 assert(testG ? mem.line == line : true); line = -1; 2905 assert(testG ? fatalLog : true); 2906 fatalLog = false; 2907 } 2908 } 2909 } 2910 } 2911 } 2912 2913 // Issue #5 2914 @safe unittest 2915 { 2916 import std..string : indexOf; 2917 2918 auto oldunspecificLogger = sharedLog; 2919 2920 scope(exit) 2921 { 2922 sharedLog = oldunspecificLogger; 2923 globalLogLevel = LogLevel.all; 2924 } 2925 2926 auto tl = new TestLogger(LogLevel.info); 2927 sharedLog = tl; 2928 2929 trace("trace"); 2930 assert(tl.msg.indexOf("trace") == -1); 2931 } 2932 2933 // Issue #5 2934 @safe unittest 2935 { 2936 import std.experimental.logger.multilogger : MultiLogger; 2937 import std..string : indexOf; 2938 2939 stdThreadLocalLog.logLevel = LogLevel.all; 2940 2941 auto oldunspecificLogger = sharedLog; 2942 2943 scope(exit) 2944 { 2945 sharedLog = oldunspecificLogger; 2946 globalLogLevel = LogLevel.all; 2947 } 2948 2949 auto logger = new MultiLogger(LogLevel.error); 2950 2951 auto tl = new TestLogger(LogLevel.info); 2952 logger.insertLogger("required", tl); 2953 sharedLog = logger; 2954 2955 trace("trace"); 2956 assert(tl.msg.indexOf("trace") == -1); 2957 info("info"); 2958 assert(tl.msg.indexOf("info") == -1); 2959 error("error"); 2960 assert(tl.msg.indexOf("error") == 0); 2961 } 2962 2963 @system unittest 2964 { 2965 import std.exception : assertThrown; 2966 auto tl = new TestLogger(); 2967 assertThrown!Throwable(tl.fatal("fatal")); 2968 } 2969 2970 // log objects with non-safe toString 2971 @system unittest 2972 { 2973 struct Test 2974 { 2975 string toString() const @system 2976 { 2977 return "test"; 2978 } 2979 } 2980 2981 auto tl = new TestLogger(); 2982 tl.info(Test.init); 2983 assert(tl.msg == "test"); 2984 } 2985 2986 // Workaround for atomics not allowed in @safe code 2987 private auto trustedLoad(T)(ref shared T value) @trusted 2988 { 2989 import core.atomic : atomicLoad, MemoryOrder; 2990 return atomicLoad!(MemoryOrder.acq)(value); 2991 } 2992 2993 // ditto 2994 private void trustedStore(T)(ref shared T dst, ref T src) @trusted 2995 { 2996 import core.atomic : atomicStore, MemoryOrder; 2997 atomicStore!(MemoryOrder.rel)(dst, src); 2998 } 2999 3000 // check that thread-local logging does not propagate 3001 // to shared logger 3002 @system unittest 3003 { 3004 import core.atomic, core.thread, std.concurrency; 3005 3006 static shared logged_count = 0; 3007 3008 class TestLog : Logger 3009 { 3010 Tid tid; 3011 3012 this() 3013 { 3014 super (LogLevel.trace); 3015 this.tid = thisTid; 3016 } 3017 3018 override void writeLogMsg(ref LogEntry payload) @trusted 3019 { 3020 assert(thisTid == this.tid); 3021 atomicOp!"+="(logged_count, 1); 3022 } 3023 } 3024 3025 class IgnoredLog : Logger 3026 { 3027 this() 3028 { 3029 super (LogLevel.trace); 3030 } 3031 3032 override void writeLogMsg(ref LogEntry payload) @trusted 3033 { 3034 assert(false); 3035 } 3036 } 3037 3038 auto oldSharedLog = sharedLog; 3039 scope(exit) 3040 { 3041 sharedLog = oldSharedLog; 3042 } 3043 3044 sharedLog = new IgnoredLog; 3045 Thread[] spawned; 3046 3047 foreach (i; 0 .. 4) 3048 { 3049 spawned ~= new Thread({ 3050 stdThreadLocalLog = new TestLog; 3051 trace("zzzzzzzzzz"); 3052 }); 3053 spawned[$-1].start(); 3054 } 3055 3056 foreach (t; spawned) 3057 t.join(); 3058 3059 assert(atomicOp!"=="(logged_count, 4)); 3060 } 3061 3062 @safe unittest 3063 { 3064 auto dl = cast(FileLogger) sharedLog; 3065 assert(dl !is null); 3066 assert(dl.logLevel == LogLevel.all); 3067 assert(globalLogLevel == LogLevel.all); 3068 3069 auto tl = cast(StdForwardLogger) stdThreadLocalLog; 3070 assert(tl !is null); 3071 stdThreadLocalLog.logLevel = LogLevel.all; 3072 } 3073 3074 // https://issues.dlang.org/show_bug.cgi?id=14940 3075 @safe unittest 3076 { 3077 import std.typecons : Nullable; 3078 3079 Nullable!int a = 1; 3080 auto l = new TestLogger(); 3081 l.infof("log: %s", a); 3082 assert(l.msg == "log: 1"); 3083 } 3084 3085 // Ensure @system toString methods work 3086 @system unittest 3087 { 3088 enum SystemToStringMsg = "SystemToString"; 3089 static struct SystemToString 3090 { 3091 string toString() @system 3092 { 3093 return SystemToStringMsg; 3094 } 3095 } 3096 3097 auto tl = new TestLogger(); 3098 3099 SystemToString sts; 3100 tl.logf("%s", sts); 3101 assert(tl.msg == SystemToStringMsg); 3102 } 3103 3104 // https://issues.dlang.org/show_bug.cgi?id=17328 3105 @safe unittest 3106 { 3107 import std.format : format; 3108 3109 ubyte[] data = [0]; 3110 string s = format("%(%02x%)", data); // format 00 3111 assert(s == "00"); 3112 3113 auto tl = new TestLogger(); 3114 3115 tl.infof("%(%02x%)", data); // infof 000 3116 3117 size_t i; 3118 string fs = tl.msg; 3119 for (; i < s.length; ++i) 3120 { 3121 assert(s[s.length - 1 - i] == fs[fs.length - 1 - i], fs); 3122 } 3123 assert(fs.length == 2); 3124 } 3125 3126 // https://issues.dlang.org/show_bug.cgi?id=15954 3127 @safe unittest 3128 { 3129 import std.conv : to; 3130 auto tl = new TestLogger(); 3131 tl.log("123456789".to!wstring); 3132 assert(tl.msg == "123456789"); 3133 } 3134 3135 // https://issues.dlang.org/show_bug.cgi?id=16256 3136 @safe unittest 3137 { 3138 import std.conv : to; 3139 auto tl = new TestLogger(); 3140 tl.log("123456789"d); 3141 assert(tl.msg == "123456789"); 3142 } 3143 3144 // https://issues.dlang.org/show_bug.cgi?id=15517 3145 @system unittest 3146 { 3147 import std.file : exists, remove, tempDir; 3148 import std.path : buildPath; 3149 import std.stdio : File; 3150 import std..string : indexOf; 3151 3152 string fn = tempDir.buildPath("logfile.log"); 3153 if (exists(fn)) 3154 { 3155 remove(fn); 3156 } 3157 3158 auto oldShared = sharedLog; 3159 scope(exit) 3160 { 3161 sharedLog = oldShared; 3162 if (exists(fn)) 3163 { 3164 remove(fn); 3165 } 3166 } 3167 3168 auto ts = [ "Test log 1", "Test log 2", "Test log 3"]; 3169 3170 auto fl = new FileLogger(fn); 3171 sharedLog = fl; 3172 assert(exists(fn)); 3173 3174 foreach (t; ts) 3175 { 3176 log(t); 3177 } 3178 3179 auto f = File(fn); 3180 auto l = f.byLine(); 3181 assert(!l.empty); 3182 size_t idx; 3183 foreach (it; l) 3184 { 3185 assert(it.indexOf(ts[idx]) != -1, it); 3186 ++idx; 3187 } 3188 3189 assert(exists(fn)); 3190 fl.file.close(); 3191 }