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