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