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