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