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("&nbsp;"); } (); // 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("&lt;"); break;
628 				case '>': dst.put("&gt;"); break;
629 				case '&': dst.put("&amp;"); 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 }