1 /** 2 Central logging facility for vibe. 3 4 Copyright: © 2012-2014 RejectedSoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module vibe.core.log; 9 10 import vibe.core.args; 11 import vibe.core.concurrency : ScopedLock, lock; 12 import vibe.core.sync; 13 14 import std.algorithm; 15 import std.array; 16 import std.datetime; 17 import std.format; 18 import std.stdio; 19 import core.atomic; 20 import core.thread; 21 22 import std.traits : isSomeString; 23 24 /** 25 Sets the minimum log level to be printed using the default console logger. 26 27 This level applies to the default stdout/stderr logger only. 28 */ 29 void setLogLevel(LogLevel level) 30 nothrow @safe { 31 if (ss_stdoutLogger) 32 ss_stdoutLogger.lock().minLevel = level; 33 } 34 35 36 /** 37 Sets the log format used for the default console logger. 38 39 This level applies to the default stdout/stderr logger only. 40 41 Params: 42 fmt = The log format for the stderr (default is `FileLogger.Format.thread`) 43 infoFmt = The log format for the stdout (default is `FileLogger.Format.plain`) 44 */ 45 void setLogFormat(FileLogger.Format fmt, FileLogger.Format infoFmt = FileLogger.Format.plain) 46 nothrow @safe { 47 if (ss_stdoutLogger) { 48 auto l = ss_stdoutLogger.lock(); 49 l.format = fmt; 50 l.infoFormat = infoFmt; 51 } 52 } 53 54 55 /** 56 Sets a log file for disk file logging. 57 58 Multiple calls to this function will register multiple log 59 files for output. 60 */ 61 void setLogFile(string filename, LogLevel min_level = LogLevel.error) 62 { 63 auto logger = cast(shared)new FileLogger(filename); 64 { 65 auto l = logger.lock(); 66 l.minLevel = min_level; 67 l.format = FileLogger.Format.threadTime; 68 } 69 registerLogger(logger); 70 } 71 72 73 /** 74 Registers a new logger instance. 75 76 The specified Logger will receive all log messages in its Logger.log 77 method after it has been registered. 78 79 Examples: 80 --- 81 auto logger = cast(shared)new HTMLLogger("log.html"); 82 logger.lock().format = FileLogger.Format.threadTime; 83 registerLogger(logger); 84 --- 85 86 See_Also: deregisterLogger 87 */ 88 void registerLogger(shared(Logger) logger) 89 nothrow { 90 ss_loggers ~= logger; 91 } 92 93 94 /** 95 Deregisters an active logger instance. 96 97 See_Also: registerLogger 98 */ 99 void deregisterLogger(shared(Logger) logger) 100 nothrow { 101 for (size_t i = 0; i < ss_loggers.length; ) { 102 if (ss_loggers[i] !is logger) i++; 103 else ss_loggers = ss_loggers[0 .. i] ~ ss_loggers[i+1 .. $]; 104 } 105 } 106 107 108 /** 109 Logs a message. 110 111 Params: 112 level = The log level for the logged message 113 fmt = See http://dlang.org/phobos/std_format.html#format-string 114 args = Any input values needed for formatting 115 */ 116 void log(LogLevel level, /*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) 117 nothrow if (isSomeString!S) 118 { 119 static assert(level != LogLevel.none); 120 try { 121 foreach (l; getLoggers()) 122 if (l.minLevel <= level) { // WARNING: TYPE SYSTEM HOLE: accessing field of shared class! 123 auto ll = l.lock(); 124 auto rng = LogOutputRange(ll, file, line, level); 125 /*() @trusted {*/ rng.formattedWrite(fmt, args); //} (); // formattedWrite is not @safe at least up to 2.068.0 126 rng.finalize(); 127 } 128 } catch(Exception e) debug assert(false, e.msg); 129 } 130 /// ditto 131 void logTrace(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.trace/*, mod, func*/, file, line)(fmt, args); } 132 /// ditto 133 void logDebugV(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.debugV/*, mod, func*/, file, line)(fmt, args); } 134 /// ditto 135 void logDebug(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.debug_/*, mod, func*/, file, line)(fmt, args); } 136 /// ditto 137 void logDiagnostic(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.diagnostic/*, mod, func*/, file, line)(fmt, args); } 138 /// ditto 139 void logInfo(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.info/*, mod, func*/, file, line)(fmt, args); } 140 /// ditto 141 void logWarn(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.warn/*, mod, func*/, file, line)(fmt, args); } 142 /// ditto 143 void logError(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.error/*, mod, func*/, file, line)(fmt, args); } 144 /// ditto 145 void logCritical(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.critical/*, mod, func*/, file, line)(fmt, args); } 146 /// ditto 147 void logFatal(string file = __FILE__, int line = __LINE__, S, T...)(S fmt, lazy T args) nothrow { log!(LogLevel.fatal, file, line)(fmt, args); } 148 149 /// 150 @safe unittest { 151 void test() nothrow 152 { 153 logInfo("Hello, World!"); 154 logWarn("This may not be %s.", "good"); 155 log!(LogLevel.info)("This is a %s.", "test"); 156 } 157 } 158 159 /// Specifies the log level for a particular log message. 160 enum LogLevel { 161 trace, /// Developer information for locating events when no useful stack traces are available 162 debugV, /// Developer information useful for algorithm debugging - for verbose output 163 debug_, /// Developer information useful for algorithm debugging 164 diagnostic, /// Extended user information (e.g. for more detailed error information) 165 info, /// Informational message for normal user education 166 warn, /// Unexpected condition that could indicate an error but has no direct consequences 167 error, /// Normal error that is handled gracefully 168 critical, /// Error that severely influences the execution of the application 169 fatal, /// Error that forces the application to terminate 170 none, /// Special value used to indicate no logging when set as the minimum log level 171 172 verbose1 = diagnostic, /// Alias for diagnostic messages 173 verbose2 = debug_, /// Alias for debug messages 174 verbose3 = debugV, /// Alias for verbose debug messages 175 verbose4 = trace, /// Alias for trace messages 176 } 177 178 /// Represents a single logged line 179 struct LogLine { 180 string mod; 181 string func; 182 string file; 183 int line; 184 LogLevel level; 185 Thread thread; 186 string threadName; 187 uint threadID; 188 Fiber fiber; 189 uint fiberID; 190 SysTime time; 191 string text; /// Legacy field used in `Logger.log` 192 } 193 194 /// Abstract base class for all loggers 195 class Logger { 196 LogLevel minLevel = LogLevel.min; 197 198 /** Whether the logger can handle multiple lines in a single beginLine/endLine. 199 200 By default log text with newlines gets split into multiple log lines. 201 */ 202 protected bool multilineLogger = false; 203 204 private { 205 LogLine m_curLine; 206 Appender!string m_curLineText; 207 } 208 209 final bool acceptsLevel(LogLevel value) nothrow pure @safe { return value >= this.minLevel; } 210 211 /** Legacy logging interface relying on dynamic memory allocation. 212 213 Override `beginLine`, `put`, `endLine` instead for a more efficient and 214 possibly allocation-free implementation. 215 */ 216 void log(ref LogLine line) @safe {} 217 218 /// Starts a new log line. 219 void beginLine(ref LogLine line_info) 220 @safe { 221 m_curLine = line_info; 222 m_curLineText = appender!string(); 223 } 224 225 /// Writes part of a log line message. 226 void put(scope const(char)[] text) 227 @safe { 228 m_curLineText.put(text); 229 } 230 231 /// Finalizes a log line. 232 void endLine() 233 @safe { 234 m_curLine.text = m_curLineText.data; 235 log(m_curLine); 236 m_curLine.text = null; 237 m_curLineText = Appender!string.init; 238 } 239 } 240 241 242 /** 243 Plain-text based logger for logging to regular files or stdout/stderr 244 */ 245 final class FileLogger : Logger { 246 /// The log format used by the FileLogger 247 enum Format { 248 plain, /// Output only the plain log message 249 thread, /// Prefix "[thread-id:fiber-id loglevel]" 250 threadTime /// Prefix "[thread-id:fiber-id timestamp loglevel]" 251 } 252 253 private { 254 File m_infoFile; 255 File m_diagFile; 256 File m_curFile; 257 } 258 259 Format format = Format.thread; 260 Format infoFormat = Format.plain; 261 262 this(File info_file, File diag_file) 263 { 264 m_infoFile = info_file; 265 m_diagFile = diag_file; 266 } 267 268 this(string filename) 269 { 270 m_infoFile = File(filename, "ab"); 271 m_diagFile = m_infoFile; 272 } 273 274 override void beginLine(ref LogLine msg) 275 @trusted // FILE isn't @safe (as of DMD 2.065) 276 { 277 string pref; 278 final switch (msg.level) { 279 case LogLevel.trace: pref = "trc"; m_curFile = m_diagFile; break; 280 case LogLevel.debugV: pref = "dbv"; m_curFile = m_diagFile; break; 281 case LogLevel.debug_: pref = "dbg"; m_curFile = m_diagFile; break; 282 case LogLevel.diagnostic: pref = "dia"; m_curFile = m_diagFile; break; 283 case LogLevel.info: pref = "INF"; m_curFile = m_infoFile; break; 284 case LogLevel.warn: pref = "WRN"; m_curFile = m_diagFile; break; 285 case LogLevel.error: pref = "ERR"; m_curFile = m_diagFile; break; 286 case LogLevel.critical: pref = "CRITICAL"; m_curFile = m_diagFile; break; 287 case LogLevel.fatal: pref = "FATAL"; m_curFile = m_diagFile; break; 288 case LogLevel.none: assert(false); 289 } 290 291 auto fmt = (m_curFile is m_diagFile) ? this.format : this.infoFormat; 292 293 final switch (fmt) { 294 case Format.plain: break; 295 case Format.thread: m_curFile.writef("[%08X:%08X %s] ", msg.threadID, msg.fiberID, pref); break; 296 case Format.threadTime: 297 auto tm = msg.time; 298 static if (is(typeof(tm.fracSecs))) auto msecs = tm.fracSecs.total!"msecs"; // 2.069 has deprecated "fracSec" 299 else auto msecs = tm.fracSec.msecs; 300 m_curFile.writef("[%08X:%08X %d.%02d.%02d %02d:%02d:%02d.%03d %s] ", 301 msg.threadID, msg.fiberID, 302 tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second, msecs, 303 pref); 304 break; 305 } 306 } 307 308 override void put(scope const(char)[] text) 309 { 310 m_curFile.write(text); 311 } 312 313 override void endLine() 314 { 315 m_curFile.writeln(); 316 m_curFile.flush(); 317 } 318 } 319 320 /** 321 Logger implementation for logging to an HTML file with dynamic filtering support. 322 */ 323 final class HTMLLogger : Logger { 324 private { 325 File m_logFile; 326 } 327 328 this(string filename = "log.html") 329 { 330 m_logFile = File(filename, "wt"); 331 writeHeader(); 332 } 333 334 ~this() 335 { 336 //version(FinalizerDebug) writeln("HtmlLogWritet.~this"); 337 writeFooter(); 338 m_logFile.close(); 339 //version(FinalizerDebug) writeln("HtmlLogWritet.~this out"); 340 } 341 342 @property void minLogLevel(LogLevel value) pure nothrow @safe { this.minLevel = value; } 343 344 override void beginLine(ref LogLine msg) 345 @trusted // FILE isn't @safe (as of DMD 2.065) 346 { 347 if( !m_logFile.isOpen ) return; 348 349 final switch (msg.level) { 350 case LogLevel.none: assert(false); 351 case LogLevel.trace: m_logFile.write(`<div class="trace">`); break; 352 case LogLevel.debugV: m_logFile.write(`<div class="debugv">`); break; 353 case LogLevel.debug_: m_logFile.write(`<div class="debug">`); break; 354 case LogLevel.diagnostic: m_logFile.write(`<div class="diagnostic">`); break; 355 case LogLevel.info: m_logFile.write(`<div class="info">`); break; 356 case LogLevel.warn: m_logFile.write(`<div class="warn">`); break; 357 case LogLevel.error: m_logFile.write(`<div class="error">`); break; 358 case LogLevel.critical: m_logFile.write(`<div class="critical">`); break; 359 case LogLevel.fatal: m_logFile.write(`<div class="fatal">`); break; 360 } 361 m_logFile.writef(`<div class="timeStamp">%s</div>`, msg.time.toISOExtString()); 362 if (msg.thread) 363 m_logFile.writef(`<div class="threadName">%s</div>`, msg.thread.name); 364 m_logFile.write(`<div class="message">`); 365 } 366 367 override void put(scope const(char)[] text) 368 { 369 auto dst = () @trusted { return m_logFile.lockingTextWriter(); } (); // LockingTextWriter not @safe for DMD 2.066 370 while (!text.empty && (text.front == ' ' || text.front == '\t')) { 371 foreach (i; 0 .. text.front == ' ' ? 1 : 4) 372 () @trusted { dst.put(" "); } (); // LockingTextWriter not @safe for DMD 2.066 373 text.popFront(); 374 } 375 () @trusted { filterHTMLEscape(dst, text); } (); // LockingTextWriter not @safe for DMD 2.066 376 } 377 378 override void endLine() 379 { 380 () @trusted { // not @safe for DMD 2.066 381 m_logFile.write(`</div>`); 382 m_logFile.writeln(`</div>`); 383 } (); 384 m_logFile.flush(); 385 } 386 387 private void writeHeader(){ 388 if( !m_logFile.isOpen ) return; 389 390 m_logFile.writeln( 391 `<html> 392 <head> 393 <title>HTML Log</title> 394 <style content="text/css"> 395 .trace { position: relative; color: #E0E0E0; font-size: 9pt; } 396 .debugv { position: relative; color: #E0E0E0; font-size: 9pt; } 397 .debug { position: relative; color: #808080; } 398 .diagnostic { position: relative; color: #808080; } 399 .info { position: relative; color: black; } 400 .warn { position: relative; color: #E08000; } 401 .error { position: relative; color: red; } 402 .critical { position: relative; color: red; background-color: black; } 403 .fatal { position: relative; color: red; background-color: black; } 404 405 .log { margin-left: 10pt; } 406 .code { 407 font-family: "Courier New"; 408 background-color: #F0F0F0; 409 border: 1px solid gray; 410 margin-bottom: 10pt; 411 margin-left: 30pt; 412 margin-right: 10pt; 413 padding-left: 0pt; 414 } 415 416 div.timeStamp { 417 position: absolute; 418 width: 150pt; 419 } 420 div.threadName { 421 position: absolute; 422 top: 0pt; 423 left: 150pt; 424 width: 100pt; 425 } 426 div.message { 427 position: relative; 428 top: 0pt; 429 left: 250pt; 430 } 431 body { 432 font-family: Tahoma, Arial, sans-serif; 433 font-size: 10pt; 434 } 435 </style> 436 <script language="JavaScript"> 437 function enableStyle(i){ 438 var style = document.styleSheets[0].cssRules[i].style; 439 style.display = "block"; 440 } 441 442 function disableStyle(i){ 443 var style = document.styleSheets[0].cssRules[i].style; 444 style.display = "none"; 445 } 446 447 function updateLevels(){ 448 var sel = document.getElementById("Level"); 449 var level = sel.value; 450 for( i = 0; i < level; i++ ) disableStyle(i); 451 for( i = level; i < 5; i++ ) enableStyle(i); 452 } 453 </script> 454 </head> 455 <body style="padding: 0px; margin: 0px;" onLoad="updateLevels(); updateCode();"> 456 <div style="position: fixed; z-index: 100; padding: 4pt; width:100%; background-color: lightgray; border-bottom: 1px solid black;"> 457 <form style="margin: 0px;"> 458 Minimum Log Level: 459 <select id="Level" onChange="updateLevels()"> 460 <option value="0">Trace</option> 461 <option value="1">Verbose</option> 462 <option value="2">Debug</option> 463 <option value="3">Diagnostic</option> 464 <option value="4">Info</option> 465 <option value="5">Warn</option> 466 <option value="6">Error</option> 467 <option value="7">Critical</option> 468 <option value="8">Fatal</option> 469 </select> 470 </form> 471 </div> 472 <div style="height: 30pt;"></div> 473 <div class="log">`); 474 m_logFile.flush(); 475 } 476 477 private void writeFooter(){ 478 if( !m_logFile.isOpen ) return; 479 480 m_logFile.writeln( 481 ` </div> 482 </body> 483 </html>`); 484 m_logFile.flush(); 485 } 486 487 private static void filterHTMLEscape(R, S)(ref R dst, S str) 488 { 489 for (;!str.empty;str.popFront()) { 490 auto ch = str.front; 491 switch (ch) { 492 default: dst.put(ch); break; 493 case '<': dst.put("<"); break; 494 case '>': dst.put(">"); break; 495 case '&': dst.put("&"); break; 496 } 497 } 498 } 499 } 500 501 import std.conv; 502 /** 503 A logger that logs in syslog format according to RFC 5424. 504 505 Messages can be logged to files (via file streams) or over the network (via 506 TCP or SSL streams). 507 508 Standards: Conforms to RFC 5424. 509 */ 510 final class SyslogLogger : Logger { 511 import vibe.core.stream; 512 private { 513 string m_hostName; 514 string m_appName; 515 OutputStream m_ostream; 516 Facility m_facility; 517 } 518 519 /// Facilities 520 enum Facility { 521 kern, /// kernel messages 522 user, /// user-level messages 523 mail, /// mail system 524 daemon, /// system daemons 525 auth, /// security/authorization messages 526 syslog, /// messages generated internally by syslogd 527 lpr, /// line printer subsystem 528 news, /// network news subsystem 529 uucp, /// UUCP subsystem 530 clockDaemon, /// clock daemon 531 authpriv, /// security/authorization messages 532 ftp, /// FTP daemon 533 ntp, /// NTP subsystem 534 logAudit, /// log audit 535 logAlert, /// log alert 536 cron, /// clock daemon 537 local0, /// local use 0 538 local1, /// local use 1 539 local2, /// local use 2 540 local3, /// local use 3 541 local4, /// local use 4 542 local5, /// local use 5 543 local6, /// local use 6 544 local7, /// local use 7 545 } 546 547 /// Severities 548 private enum Severity { 549 emergency, /// system is unusable 550 alert, /// action must be taken immediately 551 critical, /// critical conditions 552 error, /// error conditions 553 warning, /// warning conditions 554 notice, /// normal but significant condition 555 info, /// informational messages 556 debug_, /// debug-level messages 557 } 558 559 /// syslog message format (version 1) 560 /// see section 6 in RFC 5424 561 private enum SYSLOG_MESSAGE_FORMAT_VERSION1 = "<%.3s>1 %s %.255s %.48s %.128s %.32s %s %s"; 562 /// 563 private enum NILVALUE = "-"; 564 /// 565 private enum BOM = x"EFBBBF"; 566 567 /** 568 Construct a SyslogLogger. 569 570 The log messages are sent to the given OutputStream stream using the given 571 Facility facility.Optionally the appName and hostName can be set. The 572 appName defaults to null. The hostName defaults to hostName(). 573 574 Note that the passed stream's write function must not use logging with 575 a level for that this Logger's acceptsLevel returns true. Because this 576 Logger uses the stream's write function when it logs and would hence 577 log forevermore. 578 */ 579 this(OutputStream stream, Facility facility, string appName = null, string hostName = hostName()) 580 { 581 m_hostName = hostName != "" ? hostName : NILVALUE; 582 m_appName = appName != "" ? appName : NILVALUE; 583 m_ostream = stream; 584 m_facility = facility; 585 this.minLevel = LogLevel.debug_; 586 } 587 588 /** 589 Logs the given LogLine msg. 590 591 It uses the msg's time, level, and text field. 592 */ 593 override void beginLine(ref LogLine msg) 594 @trusted { // OutputStream isn't @safe 595 auto tm = msg.time; 596 import core.time; 597 // at most 6 digits for fractional seconds according to RFC 598 static if (is(typeof(tm.fracSecs))) tm.fracSecs = tm.fracSecs.total!"usecs".dur!"usecs"; 599 else tm.fracSec = FracSec.from!"usecs"(tm.fracSec.usecs); 600 auto timestamp = tm.toISOExtString(); 601 602 Severity syslogSeverity; 603 // map LogLevel to syslog's severity 604 final switch(msg.level) { 605 case LogLevel.none: assert(false); 606 case LogLevel.trace: return; 607 case LogLevel.debugV: return; 608 case LogLevel.debug_: syslogSeverity = Severity.debug_; break; 609 case LogLevel.diagnostic: syslogSeverity = Severity.info; break; 610 case LogLevel.info: syslogSeverity = Severity.notice; break; 611 case LogLevel.warn: syslogSeverity = Severity.warning; break; 612 case LogLevel.error: syslogSeverity = Severity.error; break; 613 case LogLevel.critical: syslogSeverity = Severity.critical; break; 614 case LogLevel.fatal: syslogSeverity = Severity.alert; break; 615 } 616 617 assert(msg.level >= LogLevel.debug_); 618 import std.conv : to; // temporary workaround for issue 1016 (DMD cross-module template overloads error out before second attempted module) 619 auto priVal = m_facility * 8 + syslogSeverity; 620 621 alias procId = NILVALUE; 622 alias msgId = NILVALUE; 623 alias structuredData = NILVALUE; 624 625 auto text = msg.text; 626 import std.format : formattedWrite; 627 auto str = appender!string(); // FIXME: avoid GC allocation 628 (&str).formattedWrite(SYSLOG_MESSAGE_FORMAT_VERSION1, priVal, 629 timestamp, m_hostName, BOM ~ m_appName, procId, msgId, 630 structuredData, BOM); 631 m_ostream.write(str.data); 632 } 633 634 override void put(scope const(char)[] text) 635 @trusted { 636 m_ostream.write(text); 637 } 638 639 override void endLine() 640 @trusted { 641 m_ostream.write("\n"); 642 m_ostream.flush(); 643 } 644 645 unittest 646 { 647 import vibe.core.file; 648 auto fstream = createTempFile(); 649 auto logger = new SyslogLogger(fstream, Facility.local1, "appname", null); 650 LogLine msg; 651 import std.datetime; 652 import core.thread; 653 static if (is(typeof(SysTime.init.fracSecs))) auto fs = 1.dur!"usecs"; 654 else auto fs = FracSec.from!"usecs"(1); 655 msg.time = SysTime(DateTime(0, 1, 1, 0, 0, 0), fs); 656 657 foreach (lvl; [LogLevel.debug_, LogLevel.diagnostic, LogLevel.info, LogLevel.warn, LogLevel.error, LogLevel.critical, LogLevel.fatal]) { 658 msg.level = lvl; 659 logger.beginLine(msg); 660 logger.put("αβγ"); 661 logger.endLine(); 662 } 663 fstream.close(); 664 665 import std.file; 666 import std.string; 667 auto lines = splitLines(readText(fstream.path().toNativeString()), KeepTerminator.yes); 668 assert(lines.length == 7); 669 assert(lines[0] == "<143>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n"); 670 assert(lines[1] == "<142>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n"); 671 assert(lines[2] == "<141>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n"); 672 assert(lines[3] == "<140>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n"); 673 assert(lines[4] == "<139>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n"); 674 assert(lines[5] == "<138>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n"); 675 assert(lines[6] == "<137>1 0000-01-01T00:00:00.000001 - " ~ BOM ~ "appname - - - " ~ BOM ~ "αβγ\n"); 676 removeFile(fstream.path().toNativeString()); 677 } 678 } 679 680 /// Returns: this host's host name. 681 /// 682 /// If the host name cannot be determined the function returns null. 683 private string hostName() 684 { 685 string hostName; 686 687 version (Posix) { 688 import core.sys.posix.sys.utsname; 689 import core.sys.posix.unistd : gethostname; 690 utsname name; 691 if (uname(&name)) return hostName; 692 hostName = name.nodename.to!string(); 693 694 import std.socket; 695 auto ih = new InternetHost; 696 if (!ih.getHostByName(hostName)) return hostName; 697 hostName = ih.name; 698 699 if (!hostName.length) { 700 char[256] buf; 701 if (gethostname(buf.ptr, cast(int) buf.length) == 0) { 702 ubyte len; 703 for (; len < buf.length && buf[len] != 0; len++) {} 704 hostName = buf[0 .. len].idup; 705 } 706 } 707 } else version (Windows) { 708 import core.sys.windows.winbase; 709 import core.sys.windows.winsock2 : gethostname; 710 wchar[512] buf; 711 uint size = cast(uint) buf.length; 712 if (GetComputerNameExW(COMPUTER_NAME_FORMAT.ComputerNamePhysicalDnsFullyQualified, buf.ptr, &size)) { 713 hostName = buf[0 .. size].to!string(); 714 } 715 716 if (!hostName.length) { 717 char[256] name; 718 if (gethostname(name.ptr, cast(int) name.length) == 0) { 719 ubyte len; 720 for (; len < name.length && name[len] != 0; len++) {} 721 hostName = name[0 .. len].idup; 722 } 723 } 724 } 725 726 return hostName; 727 } 728 729 private { 730 __gshared shared(Logger)[] ss_loggers; 731 shared(FileLogger) ss_stdoutLogger; 732 } 733 734 private shared(Logger)[] getLoggers() nothrow @trusted { return ss_loggers; } 735 736 package void initializeLogModule() 737 { 738 version (Windows) { 739 version (VibeWinrtDriver) enum disable_stdout = true; 740 else { 741 enum disable_stdout = false; 742 if (!GetStdHandle(STD_OUTPUT_HANDLE) || !GetStdHandle(STD_ERROR_HANDLE)) return; 743 } 744 } else enum disable_stdout = false; 745 746 static if (!disable_stdout) { 747 ss_stdoutLogger = cast(shared)new FileLogger(stdout, stderr); 748 { 749 auto l = ss_stdoutLogger.lock(); 750 l.minLevel = LogLevel.info; 751 l.format = FileLogger.Format.plain; 752 } 753 registerLogger(ss_stdoutLogger); 754 755 bool[4] verbose; 756 version (VibeNoDefaultArgs) {} 757 else { 758 readOption("verbose|v" , &verbose[0], "Enables diagnostic messages (verbosity level 1)."); 759 readOption("vverbose|vv", &verbose[1], "Enables debugging output (verbosity level 2)."); 760 readOption("vvv" , &verbose[2], "Enables high frequency debugging output (verbosity level 3)."); 761 readOption("vvvv" , &verbose[3], "Enables high frequency trace output (verbosity level 4)."); 762 } 763 764 foreach_reverse (i, v; verbose) 765 if (v) { 766 setLogFormat(FileLogger.Format.thread); 767 setLogLevel(cast(LogLevel)(LogLevel.diagnostic - i)); 768 break; 769 } 770 } 771 } 772 773 private struct LogOutputRange { 774 LogLine info; 775 ScopedLock!Logger* logger; 776 777 @safe: 778 779 this(ref ScopedLock!Logger logger, string file, int line, LogLevel level) 780 { 781 () @trusted { this.logger = &logger; } (); 782 try { 783 () @trusted { this.info.time = Clock.currTime(UTC()); }(); // not @safe as of 2.065 784 //this.info.mod = mod; 785 //this.info.func = func; 786 this.info.file = file; 787 this.info.line = line; 788 this.info.level = level; 789 this.info.thread = () @trusted { return Thread.getThis(); }(); // not @safe as of 2.065 790 this.info.threadID = makeid(this.info.thread); 791 this.info.fiber = () @trusted { return Fiber.getThis(); }(); // not @safe as of 2.065 792 this.info.fiberID = makeid(this.info.fiber); 793 } catch (Exception e) { 794 try { 795 () @trusted { writefln("Error during logging: %s", e.toString()); }(); // not @safe as of 2.065 796 } catch(Exception) {} 797 assert(false, "Exception during logging: "~e.msg); 798 } 799 800 this.logger.beginLine(info); 801 } 802 803 void finalize() 804 { 805 logger.endLine(); 806 } 807 808 void put(scope const(char)[] text) 809 { 810 if (text.empty) 811 return; 812 813 if (logger.multilineLogger) 814 logger.put(text); 815 else 816 { 817 auto rng = text.splitter('\n'); 818 logger.put(rng.front); 819 rng.popFront; 820 foreach (line; rng) 821 { 822 logger.endLine(); 823 logger.beginLine(info); 824 logger.put(line); 825 } 826 } 827 } 828 829 void put(char ch) @trusted { put((&ch)[0 .. 1]); } 830 831 void put(dchar ch) 832 { 833 static import std.utf; 834 835 if (ch < 128) put(cast(char)ch); 836 else { 837 char[4] buf; 838 auto len = std.utf.encode(buf, ch); 839 put(buf[0 .. len]); 840 } 841 } 842 843 private uint makeid(T)(T ptr) @trusted { return (cast(ulong)cast(void*)ptr & 0xFFFFFFFF) ^ (cast(ulong)cast(void*)ptr >> 32); } 844 } 845 846 private version (Windows) { 847 import core.sys.windows.windows; 848 enum STD_OUTPUT_HANDLE = cast(DWORD)-11; 849 enum STD_ERROR_HANDLE = cast(DWORD)-12; 850 extern(System) HANDLE GetStdHandle(DWORD nStdHandle); 851 } 852 853 unittest 854 { 855 static class TestLogger : Logger 856 { 857 string[] lines; 858 override void beginLine(ref LogLine msg) { lines.length += 1; } 859 override void put(scope const(char)[] text) { lines[$-1] ~= text; } 860 override void endLine() { } 861 } 862 auto logger = new TestLogger; 863 auto ll = (cast(shared(Logger))logger).lock(); 864 auto rng = LogOutputRange(ll, __FILE__, __LINE__, LogLevel.info); 865 rng.formattedWrite("text\nwith\nnewlines"); 866 rng.finalize(); 867 868 assert(logger.lines == ["text", "with", "newlines"]); 869 logger.lines = null; 870 logger.multilineLogger = true; 871 872 rng = LogOutputRange(ll, __FILE__, __LINE__, LogLevel.info); 873 rng.formattedWrite("text\nwith\nnewlines"); 874 rng.finalize(); 875 assert(logger.lines == ["text\nwith\nnewlines"]); 876 } 877 878 unittest { // make sure the default logger doesn't allocate/is usable within finalizers 879 bool destroyed = false; 880 881 class Test { 882 ~this() 883 { 884 logInfo("logInfo doesn't allocate."); 885 destroyed = true; 886 } 887 } 888 889 auto t = new Test; 890 destroy(t); 891 assert(destroyed); 892 }