diff --git a/posix.mak b/posix.mak index d7687ac2540..65040e7381f 100644 --- a/posix.mak +++ b/posix.mak @@ -91,7 +91,7 @@ DOCSRC = ../dlang.org WEBSITE_DIR = ../web DOC_OUTPUT_DIR = $(WEBSITE_DIR)/phobos-prerelease BIGDOC_OUTPUT_DIR = /tmp -SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) std/regex/package $(EXTRA_DOCUMENTABLES)) +SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(STD_MODULES) $(STD_NET_MODULES) $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) std/regex/package $(EXTRA_DOCUMENTABLES) $(STD_LOGGER_MODULES)) STDDOC = $(DOCSRC)/html.ddoc $(DOCSRC)/dlang.org.ddoc $(DOCSRC)/std_navbar-prerelease.ddoc $(DOCSRC)/std.ddoc $(DOCSRC)/macros.ddoc BIGSTDDOC = $(DOCSRC)/std_consolidated.ddoc $(DOCSRC)/macros.ddoc # Set DDOC, the documentation generator @@ -193,6 +193,9 @@ STD_MODULES = $(addprefix std/, array ascii base64 bigint \ STD_NET_MODULES = $(addprefix std/net/, isemail curl) +STD_LOGGER_MODULES = $(addprefix std/experimental/logger/, package core \ + filelogger nulllogger multilogger) + STD_REGEX_MODULES = $(addprefix std/regex/, package $(addprefix internal/, \ generator ir parser backtracking kickstart tests thompson)) @@ -233,7 +236,8 @@ EXTRA_MODULES += $(EXTRA_DOCUMENTABLES) $(addprefix \ # Aggregate all D modules relevant to this build D_MODULES = $(STD_MODULES) $(EXTRA_MODULES) $(STD_NET_MODULES) \ $(STD_DIGEST_MODULES) $(STD_CONTAINER_MODULES) $(STD_REGEX_MODULES) \ - $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) + $(STD_RANGE_MODULES) $(STD_ALGO_MODULES) $(STD_LOGGER_MODULES) + # Add the .d suffix to the module names D_FILES = $(addsuffix .d,$(D_MODULES)) # Aggregate all D modules over all OSs (this is for the zip file) @@ -444,6 +448,9 @@ $(DOC_OUTPUT_DIR)/std_regex_%.html : std/regex/%.d $(STDDOC) $(DOC_OUTPUT_DIR)/std_net_%.html : std/net/%.d $(STDDOC) $(DDOC) project.ddoc $(STDDOC) -Df$@ $< +$(DOC_OUTPUT_DIR)/std_experimental_logger_%.html : std/experimental/logger/%.d $(STDDOC) + $(DDOC) project.ddoc $(STDDOC) -Df$@ $< + $(DOC_OUTPUT_DIR)/std_digest_%.html : std/digest/%.d $(STDDOC) $(DDOC) project.ddoc $(STDDOC) -Df$@ $< @@ -455,6 +462,9 @@ $(DOC_OUTPUT_DIR)/%.html : %.d $(STDDOC) html : $(DOC_OUTPUT_DIR)/. $(HTMLS) $(STYLECSS_TGT) +allmod : + echo $(SRC_DOCUMENTABLES) + rsync-prerelease : html rsync -avz $(DOC_OUTPUT_DIR)/ d-programming@digitalmars.com:data/phobos-prerelease/ rsync -avz $(WEBSITE_DIR)/ d-programming@digitalmars.com:data/phobos-prerelase/ diff --git a/std/concurrency.d b/std/concurrency.d index 8f4bc406ab6..df44262a153 100644 --- a/std/concurrency.d +++ b/std/concurrency.d @@ -317,7 +317,7 @@ class TidMissingException : Exception struct Tid { private: - this( MessageBox m ) + this( MessageBox m ) @safe { mbox = m; } @@ -346,12 +346,18 @@ public: /** * Returns the caller's Tid. */ -@property Tid thisTid() +@property Tid thisTid() @safe { - if( thisInfo.ident != Tid.init ) + // TODO: remove when concurrency is safe + auto trus = delegate() @trusted + { + if( thisInfo.ident != Tid.init ) + return thisInfo.ident; + thisInfo.ident = Tid( new MessageBox ); return thisInfo.ident; - thisInfo.ident = Tid( new MessageBox ); - return thisInfo.ident; + }; + + return trus(); } /** @@ -1769,7 +1775,7 @@ private */ class MessageBox { - this() + this() @trusted /* TODO: make @safe after relevant druntime PR gets merged */ { m_lock = new Mutex; m_closed = false; diff --git a/std/experimental/logger/core.d b/std/experimental/logger/core.d new file mode 100644 index 00000000000..e5f71cb384c --- /dev/null +++ b/std/experimental/logger/core.d @@ -0,0 +1,3021 @@ +module std.experimental.logger.core; + +import std.array; +import std.stdio; +import std.conv; +import std.datetime; +import std.string; +import std.range; +import std.traits; +import std.exception; +import std.concurrency; +import std.format; +import core.atomic; +import core.sync.mutex : Mutex; + +import std.experimental.logger.filelogger; + +shared static this() +{ + stdSharedLoggerMutex = new Mutex; +} + +/** This template evaluates if the passed $(D LogLevel) is active. +The previously described version statements are used to decide if the +$(D LogLevel) is active. The version statements only influence the compile +unit they are used with, therefore this function can only disable logging this +specific compile unit. +*/ +template isLoggingActiveAt(LogLevel ll) +{ + version (StdLoggerDisableLogging) + { + enum isLoggingActiveAt = false; + } + else + { + static if (ll == LogLevel.trace) + { + version (StdLoggerDisableTrace) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.info) + { + version (StdLoggerDisableInfo) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.warning) + { + version (StdLoggerDisableWarning) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.error) + { + version (StdLoggerDisableError) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.critical) + { + version (StdLoggerDisableCritical) enum isLoggingActiveAt = false; + } + else static if (ll == LogLevel.fatal) + { + version (StdLoggerDisableFatal) enum isLoggingActiveAt = false; + } + // If `isLoggingActiveAt` didn't get defined above to false, + // we default it to true. + static if (!is(typeof(isLoggingActiveAt) == bool)) + { + enum isLoggingActiveAt = true; + } + } +} + +/// This compile-time flag is $(D true) if logging is not statically disabled. +enum isLoggingActive = isLoggingActiveAt!(LogLevel.all); + +/** This functions is used at runtime to determine if a $(D LogLevel) is +active. The same previously defined version statements are used to disable +certain levels. Again the version statements are associated with a compile +unit and can therefore not disable logging in other compile units. +pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc +*/ +bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL, + LogLevel globalLL, lazy bool condition = true) @safe +{ + switch (ll) + { + case LogLevel.trace: + version (StdLoggerDisableTrace) return false; + else break; + case LogLevel.info: + version (StdLoggerDisableInfo) return false; + else break; + case LogLevel.warning: + version (StdLoggerDisableWarning) return false; + else break; + case LogLevel.critical: + version (StdLoggerDisableCritical) return false; + else break; + case LogLevel.fatal: + version (StdLoggerDisableFatal) return false; + else break; + default: break; + } + + return ll >= globalLL + && ll >= loggerLL + && globalLL != LogLevel.off + && loggerLL != LogLevel.off + && condition; +} + +/** This template returns the $(D LogLevel) named "logLevel" of type $(D +LogLevel) defined in a user defined module where the filename has the +suffix "_loggerconfig.d". This $(D LogLevel) sets the minimal $(D LogLevel) +of the module. + +A minimal $(D LogLevel) can be defined on a per module basis. +In order to define a module $(D LogLevel) a file with a modulename +"MODULENAME_loggerconfig" must be found. If no such module exists and the +module is a nested module, it is checked if there exists a +"PARENT_MODULE_loggerconfig" module with such a symbol. +If this module exists and it contains a $(D LogLevel) called logLevel this $(D +LogLevel) will be used. This parent lookup is continued until there is no +parent module. Then the moduleLogLevel is $(D LogLevel.all). +*/ +template moduleLogLevel(string moduleName) if (!moduleName.length) +{ + // default + enum moduleLogLevel = LogLevel.all; +} + +/// +unittest +{ + static assert(moduleLogLevel!"" == LogLevel.all); +} + +/// ditto +template moduleLogLevel(string moduleName) if (moduleName.length) +{ + import std.string : format; + mixin(q{ + static if (__traits(compiles, {import %1$s : logLevel;})) + { + import %1$s : logLevel; + static assert(is(typeof(logLevel) : LogLevel), + "Expect 'logLevel' to be of Type 'LogLevel'."); + // don't enforce enum here + alias moduleLogLevel = logLevel; + } + else + // use logLevel of package or default + alias moduleLogLevel = moduleLogLevel!(parentOf(moduleName)); + }.format(moduleName ~ "_loggerconfig")); +} + +/// +unittest +{ + static assert(moduleLogLevel!"not.amodule.path" == LogLevel.all); +} + +private string parentOf(string mod) +{ + foreach_reverse (i, c; mod) + if (c == '.') return mod[0 .. i]; + return null; +} + +/* This function formates a $(D SysTime) into an $(D OutputRange). + +The $(D SysTime) is formatted similar to +$(LREF std.datatime.DateTime.toISOExtString) expect the fractional second part. +The sub second part is the upper three digest of the microsecond. +*/ +void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time) + if (isOutputRange!(OutputRange,string)) +{ + auto fsec = time.fracSec.usecs / 1000; + + formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", + time.year, time.month, time.day, time.hour, time.minute, time.second, + fsec); +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel) additionally the condition passed must be $(D true). + +Params: + ll = The $(D LogLevel) used by this log call. + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Examples: +-------------------- +log(LogLevel.warning, true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) @safe + if (args.length > 1) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, args); + } + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName, + prettyFuncName); + } + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog). + +Params: + ll = The $(D LogLevel) used by this log call. + args = The data that should be logged. + +Examples: +-------------------- +log(LogLevel.warning, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) + if (args.length > 1 && !is(Unqual!(A[0]) : bool)) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, args); + } + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName, + moduleName); + } + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the +$(D sharedLog) must be greater or equal to the $(D defaultLogLevel) +add the condition passed must be $(D true). + +Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Examples: +-------------------- +log(true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + if (args.length > 1) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, args); + } +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(T,moduleName)(stdThreadLocalLog.logLevel, + condition, arg, line, file, funcName, prettyFuncName); + } +} + +/** This function logs data. + +In order for the data to be processed the $(D LogLevel) of the +$(D sharedLog) must be greater or equal to the $(D defaultLogLevel). + +Params: + args = The data that should be logged. + +Examples: +-------------------- +log("Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if (args.length > 1 && !is(Unqual!(A[0]) : bool) + && !is(Unqual!(A[0]) == LogLevel)) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!(line, file, funcName, + prettyFuncName, moduleName)(stdThreadLocalLog.logLevel, args); + } +} + +void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file, + funcName, prettyFuncName, moduleName); + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel) additionally the condition passed must be $(D true). + +Params: + ll = The $(D LogLevel) used by this log call. + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf(LogLevel.warning, true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, msg, args); + } + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D LogLevel) of the $(D sharedLog) and the +$(D defaultLogLevel). + +Params: + ll = The $(D LogLevel) used by this log call. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf(LogLevel.warning, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy string msg, + lazy A args) +{ + static if (isLoggingActive) + { + if (ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, msg, args); + } + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D defaultLogLevel) additionally the condition +passed must be $(D true). + +Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf(true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, msg, args); + } +} + +/** This function logs data in a $(D printf)-style manner. + +In order for the data to be processed the $(D LogLevel) of the log call must +be greater or equal to the $(D defaultLogLevel). + +Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +logf("Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) +{ + static if (isLoggingActive) + { + stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, msg, args); + } +} + +/** This template provides the global log functions with the $(D LogLevel) +is encoded in the function name. + +The aliases following this template create the public names of these log +functions. +*/ +template defaultLogFunction(LogLevel ll) +{ + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) @safe + if (args.length > 0 && !is(Unqual!(A[0]) : bool)) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(args); + } + } + + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(condition, args); + } + } +} + +/** This function logs data to the $(D stdThreadLocalLog). + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D stdThreadLocalLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + args = The data that should be logged. + +Examples: +-------------------- +trace(1337, "is number"); +info(1337, "is number"); +error(1337, "is number"); +critical(1337, "is number"); +fatal(1337, "is number"); +-------------------- + +The second version of the function logs data to the $(D stdThreadLocalLog) depending +on a condition. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D stdThreadLocalLog) and +must be greater or equal than the global $(D LogLevel) additionally the +condition passed must be $(D true). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + +Examples: +-------------------- +trace(true, 1337, "is number"); +info(false, 1337, "is number"); +error(true, 1337, "is number"); +critical(false, 1337, "is number"); +fatal(true, 1337, "is number"); +-------------------- +*/ +alias trace = defaultLogFunction!(LogLevel.trace); +/// Ditto +alias info = defaultLogFunction!(LogLevel.info); +/// Ditto +alias warning = defaultLogFunction!(LogLevel.warning); +/// Ditto +alias error = defaultLogFunction!(LogLevel.error); +/// Ditto +alias critical = defaultLogFunction!(LogLevel.critical); +/// Ditto +alias fatal = defaultLogFunction!(LogLevel.fatal); + +/** This template provides the global $(D printf)-style log functions with +the $(D LogLevel) is encoded in the function name. + +The aliases following this template create the public names of the log +functions. +*/ +template defaultLogFunctionf(LogLevel ll) +{ + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(msg, args); + } + } + + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(condition, msg, args); + } + } +} + +/** This function logs data to the $(D sharedLog) in a $(D printf)-style +manner. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D sharedLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +tracef("is number %d", 1); +infof("is number %d", 2); +errorf("is number %d", 3); +criticalf("is number %d", 4); +fatalf("is number %d", 5); +-------------------- + +The second version of the function logs data to the $(D sharedLog) in a $(D +printf)-style manner. + +In order for the resulting log message to be logged the $(D LogLevel) must +be greater or equal than the $(D LogLevel) of the $(D sharedLog) and +must be greater or equal than the global $(D LogLevel). +Additionally the $(D LogLevel) must be greater or equal than the $(D LogLevel) +of the $(D stdSharedLogger). + +Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + +Examples: +-------------------- +tracef(false, "is number %d", 1); +infof(false, "is number %d", 2); +errorf(true, "is number %d", 3); +criticalf(true, "is number %d", 4); +fatalf(someFunct(), "is number %d", 5); +-------------------- +*/ +alias tracef = defaultLogFunctionf!(LogLevel.trace); +/// Ditto +alias infof = defaultLogFunctionf!(LogLevel.info); +/// Ditto +alias warningf = defaultLogFunctionf!(LogLevel.warning); +/// Ditto +alias errorf = defaultLogFunctionf!(LogLevel.error); +/// Ditto +alias criticalf = defaultLogFunctionf!(LogLevel.critical); +/// Ditto +alias fatalf = defaultLogFunctionf!(LogLevel.fatal); + +private struct MsgRange +{ + import std.traits : isSomeString, isSomeChar; + + private Logger log; + + private char[] buffer; + + this(Logger log) @safe + { + this.log = log; + } + + void put(T)(T msg) @safe + if (isSomeString!T) + { + log.logMsgPart(msg); + } + + void put(dchar elem) @safe + { + import std.utf : encode; + encode(this.buffer, elem); + log.logMsgPart(this.buffer); + } +} + +private void formatString(A...)(MsgRange oRange, A args) +{ + import std.format : formattedWrite; + + foreach (arg; args) + { + std.format.formattedWrite(oRange, "%s", arg); + } +} + +unittest +{ + void dummy() @safe + { + auto tl = new TestLogger(); + auto dst = MsgRange(tl); + formatString(dst, "aaa", "bbb"); + } + + dummy(); +} + +/** +There are eight usable logging level. These level are $(I all), $(I trace), +$(I info), $(I warning), $(I error), $(I critical), $(I fatal), and $(I off). +If a log function with $(D LogLevel.fatal) is called the shutdown handler of +that logger is called. +*/ +enum LogLevel : ubyte +{ + all = 1, /** Lowest possible assignable $(D LogLevel). */ + trace = 32, /** $(D LogLevel) for tracing the execution of the program. */ + info = 64, /** This level is used to display information about the + program. */ + warning = 96, /** warnings about the program should be displayed with this + level. */ + error = 128, /** Information about errors should be logged with this + level.*/ + critical = 160, /** Messages that inform about critical errors should be + logged with this level. */ + fatal = 192, /** Log messages that describe fatal errors should use this + level. */ + off = ubyte.max /** Highest possible $(D LogLevel). */ +} + +/** This class is the base of every logger. In order to create a new kind of +logger a deriving class needs to implement the $(D writeLogMsg) method. By +default this is not thread-safe. + +It is also possible to $(D override) the three methods $(D beginLogMsg), +$(D logMsgPart) and $(D finishLogMsg) together, this option gives more +flexibility. +*/ +abstract class Logger +{ + /** LogEntry is a aggregation combining all information associated + with a log message. This aggregation will be passed to the method + writeLogMsg. + */ + protected struct LogEntry + { + /// the filename the log function was called from + string file; + /// the line number the log function was called from + int line; + /// the name of the function the log function was called from + string funcName; + /// the pretty formatted name of the function the log function was + /// called from + string prettyFuncName; + /// the name of the module the log message is coming from + string moduleName; + /// the $(D LogLevel) associated with the log message + LogLevel logLevel; + /// thread id of the log message + Tid threadId; + /// the time the message was logged + SysTime timestamp; + /// the message of the log message + string msg; + /// A refernce to the $(D Logger) used to create this $(D LogEntry) + Logger logger; + } + + /** This constructor takes a name of type $(D string), and a $(D LogLevel). + + Every subclass of $(D Logger) has to call this constructor from there + constructor. It sets the $(D LogLevel), the name of the $(D Logger), and + creates a fatal handler. The fatal handler will throw an $(D Error) if a + log call is made with a $(D LogLevel) $(D LogLevel.fatal). + */ + this(LogLevel lv) @safe + { + this.logLevel_ = lv; + this.fatalHandler_ = delegate() { + throw new Error("A fatal log message was logged"); + }; + // TODO: remove lambda hack after relevant druntime PR gets merged + this.mutex = () @trusted { return new Mutex(); } (); + } + + /** A custom logger must implement this method in order to work in a + $(D MultiLogger) and $(D ArrayLogger). + + Params: + payload = All information associated with call to log function. + + See_Also: beginLogMsg, logMsgPart, finishLogMsg + */ + abstract protected void writeLogMsg(ref LogEntry payload) @safe; + + /* The default implementation will use an $(D std.array.appender) + internally to construct the message string. This means dynamic, + GC memory allocation. A logger can avoid this allocation by + reimplementing $(D beginLogMsg), $(D logMsgPart) and $(D finishLogMsg). + $(D beginLogMsg) is always called first, followed by any number of calls + to $(D logMsgPart) and one call to $(D finishLogMsg). + + As an example for such a custom $(D Logger) compare this: + ---------------- + class CLogger : Logger + { + override void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp) + { + ... logic here + } + + override void logMsgPart(const(char)[] msg) + { + ... logic here + } + + override void finishLogMsg() + { + ... logic here + } + + void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + } + ---------------- + */ + protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + static if (isLoggingActive) + { + msgAppender = appender!string(); + header = LogEntry(file, line, funcName, prettyFuncName, + moduleName, logLevel, threadId, timestamp, null, logger); + } + } + + /** Logs a part of the log message. */ + protected void logMsgPart(const(char)[] msg) @safe + { + static if (isLoggingActive) + { + msgAppender.put(msg); + } + } + + /** Signals that the message has been written and no more calls to + $(D logMsgPart) follow. */ + protected void finishLogMsg() @safe + { + static if (isLoggingActive) + { + header.msg = msgAppender.data; + this.writeLogMsg(header); + } + } + + /** The $(D LogLevel) determines if the log call are processed or dropped + by the $(D Logger). In order for the log call to be processed the + $(D LogLevel) of the log call must be greater or equal to the $(D LogLevel) + of the $(D logger). + + These two methods set and get the $(D LogLevel) of the used $(D Logger). + + Example: + ----------- + auto f = new FileLogger(stdout); + f.logLevel = LogLevel.info; + assert(f.logLevel == LogLevel.info); + ----------- + */ + @property final LogLevel logLevel() const pure @safe @nogc + { + return trustedLoad(this.logLevel_); + } + + /// Ditto + @property final void logLevel(const LogLevel lv) pure @safe @nogc + { + synchronized (mutex) this.logLevel_ = lv; + } + + /** This $(D delegate) is called in case a log message with + $(D LogLevel.fatal) gets logged. + + By default an $(D Error) will be thrown. + */ + @property final void delegate() fatalHandler() const pure @safe @nogc + { + synchronized (mutex) return this.fatalHandler_; + } + + /// Ditto + @property final void fatalHandler(void delegate() @safe fh) pure @safe @nogc + { + synchronized (mutex) this.fatalHandler_ = fh; + } + + /** This method allows forwarding log entries from one logger to another. + + $(D forwardMsg) will ensure proper synchronization and then call + $(D writeLogMsg). This is an API for implementing your own loggers and + should not be called by normal user code. A notable difference from other + logging functions is that the $(D globalLogLevel) wont be evaluated again + since it is assumed that the caller already checked that. + */ + void forwardMsg(ref LogEntry payload) @trusted + { + //writeln(payload); + static if (isLoggingActive) synchronized (mutex) + { + //writeln(__LINE__, " ",payload, this.logLevel_, " ", globalLogLevel); + //writeln(isLoggingEnabled(payload.logLevel, this.logLevel_, + // globalLogLevel), payload.logLevel >= this.logLevel_, + // payload.logLevel >= globalLogLevel); + if (isLoggingEnabled(payload.logLevel, this.logLevel_, + globalLogLevel)) + { + //writeln(__LINE__, " ",payload); + this.writeLogMsg(payload); + + if (payload.logLevel == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This template provides the log functions for the $(D Logger) $(D class) + with the $(D LogLevel) encoded in the function name. + + For further information see the the two functions defined inside of this + template. + + The aliases following this template create the public names of these log + functions. + */ + template memLogFunctions(LogLevel ll) + { + /** This function logs data to the used $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.trace(1337, "is number"); + s.info(1337, "is number"); + s.error(1337, "is number"); + s.critical(1337, "is number"); + s.fatal(1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if (args.length == 0 || (args.length > 0 && !is(A[0] : bool))) + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + condition. + + In order for the resulting log message to be logged the $(D LogLevel) must + be greater or equal than the $(D LogLevel) of the used $(D Logger) and + must be greater or equal than the global $(D LogLevel) additionally the + condition passed must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.trace(true, 1337, "is number"); + s.info(false, 1337, "is number"); + s.error(true, 1337, "is number"); + s.critical(false, 1337, "is number"); + s.fatal(true, 1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy A args) @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel) additionally + the passed condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The $(D printf)-style string. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stderr); + s.tracef(true, "is number %d", 1); + s.infof(true, "is number %d", 2); + s.errorf(false, "is number %d", 3); + s.criticalf(someFunc(), "is number %d", 4); + s.fatalf(true, "is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) must + be greater or equal than the $(D LogLevel) of the used $(D Logger) and + must be greater or equal than the global $(D LogLevel). + + Params: + msg = The $(D printf)-style string. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stderr); + s.tracef("is number %d", 1); + s.infof("is number %d", 2); + s.errorf("is number %d", 3); + s.criticalf("is number %d", 4); + s.fatalf("is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + @safe + { + static if (isLoggingActiveAt!ll && ll >= moduleLogLevel!moduleName) + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + } + + /// Ditto + alias trace = memLogFunctions!(LogLevel.trace).logImpl; + /// Ditto + alias tracef = memLogFunctions!(LogLevel.trace).logImplf; + /// Ditto + alias info = memLogFunctions!(LogLevel.info).logImpl; + /// Ditto + alias infof = memLogFunctions!(LogLevel.info).logImplf; + /// Ditto + alias warning = memLogFunctions!(LogLevel.warning).logImpl; + /// Ditto + alias warningf = memLogFunctions!(LogLevel.warning).logImplf; + /// Ditto + alias error = memLogFunctions!(LogLevel.error).logImpl; + /// Ditto + alias errorf = memLogFunctions!(LogLevel.error).logImplf; + /// Ditto + alias critical = memLogFunctions!(LogLevel.critical).logImpl; + /// Ditto + alias criticalf = memLogFunctions!(LogLevel.critical).logImplf; + /// Ditto + alias fatal = memLogFunctions!(LogLevel.fatal).logImpl; + /// Ditto + alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf; + + /** This method logs data with the $(D LogLevel) of the used $(D Logger). + + This method takes a $(D bool) as first argument. In order for the + data to be processed the $(D bool) must be $(D true) and the $(D LogLevel) + of the Logger must be greater or equal to the global $(D LogLevel). + + Params: + args = The data that should be logged. + condition = The condition must be $(D true) for the data to be logged. + args = The data that is to be logged. + + Returns: The logger used by the logging function as reference. + + Examples: + -------------------- + auto l = new StdioLogger(); + l.log(1337); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) @safe + if (args.length > 1) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition) && ll >= moduleLogLevel!moduleName) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel). + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.log(LogLevel.trace, 1337, "is number"); + s.log(LogLevel.info, 1337, "is number"); + s.log(LogLevel.warning, 1337, "is number"); + s.log(LogLevel.error, 1337, "is number"); + s.log(LogLevel.fatal, 1337, "is number"); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) + @safe + if (args.length > 1 && !is(Unqual!(A[0]) : bool)) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T)(const LogLevel ll, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + explicitly passed condition with the $(D LogLevel) of the used + $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel) and the condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(false, 1337, "is number"); + s.log(false, 1337, "is number"); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + @safe + if (args.length > 1) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T)(lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with the $(D LogLevel) + of the used $(D Logger). + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel). + + Params: + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.log(1337, "is number"); + s.log(info, 1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + -------------------- + */ + final void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + @safe + if (args.length > 1 + && !is(Unqual!(A[0]) : bool) + && !is(Unqual!(A[0]) == LogLevel)) + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + final void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, arg); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel) and depending on a condition in a $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel) and the + condition must be $(D true). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + condition = The condition must be $(D true) for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.info, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.warning, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.error, false ,"%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, true ,"%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) with a specific + $(D LogLevel) in a $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + must be greater or equal than the $(D LogLevel) of the used $(D Logger) + and must be greater or equal than the global $(D LogLevel). + + Params: + ll = The specific $(D LogLevel) used for logging the log message. + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, "%d %s", 1337, "is number"); + s.logf(LogLevel.info, "%d %s", 1337, "is number"); + s.logf(LogLevel.warning, "%d %s", 1337, "is number"); + s.logf(LogLevel.error, "%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, "%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used $(D Logger) depending on a + condition with the $(D LogLevel) of the used $(D Logger) in a + $(D printf)-style manner. + + In order for the resulting log message to be logged the $(D LogLevel) + of the used $(D Logger) must be greater or equal than the global + $(D LogLevel) and the condition must be $(D true). + + Params: + condition = The condition must be $(D true) for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(false ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) @safe + { + static if (isLoggingActive) synchronized (mutex) + { + //writeln(msg, args, " ", line); + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + //writeln(msg, args, " ", line); + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This method logs data to the used $(D Logger) with the $(D LogLevel) + of the this $(D Logger) in a $(D printf)-style manner. + + In order for the data to be processed the $(D LogLevel) of the $(D Logger) + must be greater or equal to the global $(D LogLevel). + + Params: + msg = The format string used for this log call. + args = The data that should be logged. + + Examples: + -------------------- + auto s = new FileLogger(stdout); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + -------------------- + */ + final void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + @safe + { + static if (isLoggingActive) synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + private void delegate() @safe fatalHandler_; + private shared LogLevel logLevel_ = LogLevel.info; + private Mutex mutex; + + protected Appender!string msgAppender; + protected LogEntry header; +} + +// Thread Global + +private __gshared Mutex stdSharedLoggerMutex; +private __gshared Logger stdSharedDefaultLogger; +private shared Logger stdSharedLogger; +private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all; + +/* This method returns the global default Logger. + * Marked @trusted because of excessive reliance on __gshared data + */ +private @property Logger defaultSharedLoggerImpl() @trusted +{ + static __gshared ubyte[__traits(classInstanceSize, FileLogger)] _buffer; + + synchronized (stdSharedLoggerMutex) + { + auto buffer = cast(ubyte[]) _buffer; + + if (stdSharedDefaultLogger is null) + { + stdSharedDefaultLogger = emplace!FileLogger(buffer, stderr, LogLevel.all); + } + } + return stdSharedDefaultLogger; +} + +/** This property sets and gets the default $(D Logger). + +Example: +------------- +sharedLog = new FileLogger(yourFile); +------------- +The example sets a new $(D FileLogger) as new $(D sharedLog). + +If at some point you want to use the original default logger again, you can +use $(D sharedLog = null;). This will put back the original. + +Note: +While getting and setting $(D sharedLog) is thread-safe, it has to be considered +that the returned reference is only a current snapshot and in the following +code, you must make sure no other thread reassigns to it between reading and +writing $(D sharedLog). +------------- +if (sharedLog !is myLogger) + sharedLog = new myLogger; +------------- +*/ +@property Logger sharedLog() @safe +{ + static auto trustedLoad(ref shared Logger logger) @trusted + { + return atomicLoad!(MemoryOrder.acq)(logger); + } + + // If we have set up our own logger use that + if (auto logger = trustedLoad(stdSharedLogger)) + { + return logger; + } + else + { + // Otherwise resort to the default logger + return defaultSharedLoggerImpl; + } +} + +/// Ditto +@property void sharedLog(Logger logger) @trusted +{ + atomicStore!(MemoryOrder.rel)(stdSharedLogger, cast(shared) logger); +} + +/** This methods get and set the global $(D LogLevel). + +Every log message with a $(D LogLevel) lower as the global $(D LogLevel) +will be discarded before it reaches $(D writeLogMessage) method of any +$(D Logger). +*/ +/* Implementation note: +For any public logging call, the global log level shall only be queried once on +entry. Otherwise when another threads changes the level, we wouls work with +different levels at different spots in the code. +*/ +@property LogLevel globalLogLevel() @safe @nogc +{ + return trustedLoad(stdLoggerGlobalLogLevel); +} + +/// Ditto +@property void globalLogLevel(LogLevel ll) @safe +{ + trustedStore(stdLoggerGlobalLogLevel, ll); +} + +// Thread Local + +/** The $(D StdForwardLogger) will always forward anything to the sharedLog. + +The $(D StdForwardLogger) will not throw if data is logged with $(D +LogLevel.fatal). +*/ +class StdForwardLogger : Logger +{ + /** The default constructor for the $(D StdForwardLogger). + + Params: + lv = The $(D LogLevel) for the $(D MultiLogger). By default the $(D + LogLevel) is $(D all). + */ + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + this.fatalHandler = delegate() {}; + } + + override protected void writeLogMsg(ref LogEntry payload) + { + sharedLog.forwardMsg(payload); + } +} + +/// +@safe unittest +{ + auto nl1 = new StdForwardLogger(LogLevel.all); +} + +/** This $(D LogLevel) is unqiue to every thread. + +The thread local $(D Logger) will use this $(D LogLevel) to filter log calls +every same way as presented earlier. +*/ +//public LogLevel threadLogLevel = LogLevel.all; +private Logger stdLoggerThreadLogger; +private Logger stdLoggerDefaultThreadLogger; + +/* This method returns the thread local default Logger. +*/ +private @property Logger stdThreadLocalLogImpl() @trusted +{ + static ubyte[__traits(classInstanceSize, StdForwardLogger)] _buffer; + + auto buffer = cast(ubyte[]) _buffer; + + if (stdLoggerDefaultThreadLogger is null) + { + stdLoggerDefaultThreadLogger = emplace!StdForwardLogger(buffer, LogLevel.all); + } + return stdLoggerDefaultThreadLogger; +} + +/** This function returns a thread unique $(D Logger), that by default +propergates all data logged to it to the $(D sharedLog). + +These properties can be used to set and get this $(D Logger). Every +modification to this $(D Logger) will only be visible in the thread the +modification has been done from. + +This $(D Logger) is called by the free standing log functions. This allows to +create thread local redirections and still use the free standing log +functions. +*/ +@property Logger stdThreadLocalLog() @safe +{ + // If we have set up our own logger use that + if (auto logger = stdLoggerThreadLogger) + return logger; + else + // Otherwise resort to the default logger + return stdThreadLocalLogImpl; +} + +/// Ditto +@property void stdThreadLocalLog(Logger logger) @safe +{ + stdLoggerThreadLogger = logger; +} + +/// Ditto +unittest +{ + Logger l = stdThreadLocalLog; + stdThreadLocalLog = new FileLogger("someFile.log"); + + stdThreadLocalLog = l; +} + +version (unittest) +{ + import std.array; + import std.ascii; + import std.random; + + @trusted package string randomString(size_t upto) + { + auto app = Appender!string(); + foreach (_ ; 0 .. upto) + app.put(letters[uniform(0, letters.length)]); + return app.data; + } +} + +@safe unittest +{ + LogLevel ll = globalLogLevel; + globalLogLevel = LogLevel.fatal; + assert(globalLogLevel == LogLevel.fatal); + globalLogLevel = ll; +} + +version (unittest) +{ + package class TestLogger : Logger + { + int line = -1; + string file = null; + string func = null; + string prettyFunc = null; + string msg = null; + LogLevel lvl; + + this(const LogLevel lv = LogLevel.info) @safe + { + super(lv); + } + + override protected void writeLogMsg(ref LogEntry payload) @safe + { + this.line = payload.line; + this.file = payload.file; + this.func = payload.funcName; + this.prettyFunc = payload.prettyFuncName; + this.lvl = payload.logLevel; + this.msg = payload.msg; + } + } + + private void testFuncNames(Logger logger) @safe + { + string s = "I'm here"; + logger.log(s); + } +} + +@safe unittest +{ + auto tl1 = new TestLogger(); + testFuncNames(tl1); + assert(tl1.func == "std.experimental.logger.core.testFuncNames", tl1.func); + assert(tl1.prettyFunc == + "void std.experimental.logger.core.testFuncNames(Logger logger) @safe", + tl1.prettyFunc); + assert(tl1.msg == "I'm here", tl1.msg); +} + +@safe unittest +{ + import std.experimental.logger.multilogger : MultiLogger; + + auto tl1 = new TestLogger; + auto tl2 = new TestLogger; + + auto ml = new MultiLogger(); + ml.insertLogger("one", tl1); + ml.insertLogger("two", tl2); + + string msg = "Hello Logger World"; + ml.log(msg); + int lineNumber = __LINE__ - 1; + assert(tl1.msg == msg); + assert(tl1.line == lineNumber); + assert(tl2.msg == msg); + assert(tl2.line == lineNumber); + + ml.removeLogger("one"); + ml.removeLogger("two"); + auto n = ml.removeLogger("one"); + assert(n is null); +} + +@safe unittest +{ + bool errorThrown = false; + auto tl = new TestLogger; + auto dele = delegate() { + errorThrown = true; + }; + tl.fatalHandler = dele; + tl.fatal(); + assert(errorThrown); +} + +@safe unittest +{ + auto l = new TestLogger(LogLevel.all); + string msg = "Hello Logger World"; + l.log(msg); + int lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.log(true, msg); + lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.log(false, msg); + assert(l.msg == msg); + assert(l.line == lineNumber, to!string(l.line)); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + l.logf(msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.logf(true, msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + l.logf(false, msg, "Yet"); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(l.logf(LogLevel.fatal, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(l.logf(LogLevel.fatal, true, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + assertNotThrown(l.logf(LogLevel.fatal, false, msg, "Yet")); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + auto oldunspecificLogger = sharedLog; + + assert(oldunspecificLogger.logLevel == LogLevel.all, + to!string(oldunspecificLogger.logLevel)); + + assert(l.logLevel == LogLevel.all); + sharedLog = l; + assert(globalLogLevel == LogLevel.all, + to!string(globalLogLevel)); + + scope(exit) + { + sharedLog = oldunspecificLogger; + } + + assert(sharedLog.logLevel == LogLevel.all); + assert(stdThreadLocalLog.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + msg = "Another message"; + log(msg); + lineNumber = __LINE__ - 1; + assert(l.logLevel == LogLevel.all); + assert(l.line == lineNumber, to!string(l.line)); + assert(l.msg == msg, l.msg); + + log(true, msg); + lineNumber = __LINE__ - 1; + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + log(false, msg); + assert(l.msg == msg); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + logf(msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + logf(true, msg, "Yet"); + lineNumber = __LINE__ - 1; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + logf(false, msg, "Yet"); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + msg = "%s Another message"; + () @trusted { + assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + () @trusted { + assertThrown!Throwable(logf(LogLevel.fatal, true, msg, "Yet")); + } (); + lineNumber = __LINE__ - 2; + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); + + assertNotThrown(logf(LogLevel.fatal, false, msg, "Yet")); + assert(l.msg == msg.format("Yet")); + assert(l.line == lineNumber); + assert(l.logLevel == LogLevel.all); +} + +unittest // default logger +{ + import std.file : remove; + string filename = randomString(32) ~ ".tempLogFile"; + FileLogger l = new FileLogger(filename); + auto oldunspecificLogger = sharedLog; + sharedLog = l; + + scope(exit) + { + remove(filename); + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + globalLogLevel = LogLevel.critical; + assert(globalLogLevel == LogLevel.critical); + + log(LogLevel.warning, notWritten); + log(LogLevel.critical, written); + + l.file.flush(); + l.file.close(); + + auto file = File(filename, "r"); + assert(!file.eof); + + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + assert(readLine.indexOf(notWritten) == -1, readLine); + file.close(); +} + +unittest +{ + import std.file : remove; + import core.memory : destroy; + string filename = randomString(32) ~ ".tempLogFile"; + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + remove(filename); + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + auto l = new FileLogger(filename); + sharedLog = l; + sharedLog.logLevel = LogLevel.critical; + + log(LogLevel.error, false, notWritten); + log(LogLevel.critical, true, written); + destroy(l); + + auto file = File(filename, "r"); + auto readLine = file.readln(); + assert(!readLine.empty, readLine); + assert(readLine.indexOf(written) != -1); + assert(readLine.indexOf(notWritten) == -1); + file.close(); +} + +@safe unittest +{ + auto tl = new TestLogger(LogLevel.all); + int l = __LINE__; + tl.info("a"); + assert(tl.line == l+1); + assert(tl.msg == "a"); + assert(tl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + l = __LINE__; + tl.trace("b"); + assert(tl.msg == "b", tl.msg); + assert(tl.line == l+1, to!string(tl.line)); +} + +// testing possible log conditions +@safe unittest +{ + auto oldunspecificLogger = sharedLog; + + auto mem = new TestLogger; + mem.fatalHandler = delegate() {}; + sharedLog = mem; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + int value = 0; + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + mem.logLevel = ll; + + foreach (cond; [true, false]) + { + foreach (condValue; [true, false]) + { + foreach (memOrG; [true, false]) + { + foreach (prntf; [true, false]) + { + foreach (ll2; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, + LogLevel.error, LogLevel.critical, + LogLevel.fatal, LogLevel.off]) + { + foreach (singleMulti; 0 .. 2) + { + int lineCall; + mem.msg = "-1"; + if (memOrG) + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + mem.logf(ll2, condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + mem.logf(ll2, condValue, + "%d %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.logf(ll2, "%s", value); + lineCall = __LINE__; + } + else + { + mem.logf(ll2, "%d %d", + value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + mem.log(ll2, condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(ll2, condValue, + to!string(value), value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.log(ll2, to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(ll2, + to!string(value), + value); + lineCall = __LINE__; + } + } + } + } + else + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + logf(ll2, condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + logf(ll2, condValue, + "%s %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + logf(ll2, "%s", value); + lineCall = __LINE__; + } + else + { + logf(ll2, "%s %s", value, + value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + log(ll2, condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + log(ll2, condValue, value, + to!string(value)); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + log(ll2, to!string(value)); + lineCall = __LINE__; + } + else + { + log(ll2, value, + to!string(value)); + lineCall = __LINE__; + } + } + } + } + + string valueStr = to!string(value); + ++value; + + bool gllOff = (gll != LogLevel.off); + bool llOff = (ll != LogLevel.off); + bool condFalse = (cond ? condValue : true); + bool ll2VSgll = (ll2 >= gll); + bool ll2VSll = (ll2 >= ll); + + bool shouldLog = gllOff && llOff && condFalse + && ll2VSgll && ll2VSll; + + /* + writefln( + "go(%b) ll2o(%b) c(%b) lg(%b) ll(%b) s(%b)" + , gll != LogLevel.off, ll2 != LogLevel.off, + cond ? condValue : true, + ll2 >= gll, ll2 >= ll, shouldLog); + */ + + + if (shouldLog) + { + assert(mem.msg.indexOf(valueStr) != -1, + format( + "lineCall(%d) gll(%u) ll(%u) ll2(%u) " ~ + "cond(%b) condValue(%b)" ~ + " memOrG(%b) shouldLog(%b) %s == %s" ~ + " %b %b %b %b %b", + lineCall, gll, ll, ll2, cond, + condValue, memOrG, shouldLog, mem.msg, + valueStr, gllOff, llOff, condFalse, + ll2VSgll, ll2VSll + )); + } + else + { + assert(mem.msg.indexOf(valueStr), + format( + "lineCall(%d) gll(%u) ll(%u) ll2(%u) " ~ + " cond(%b)condValue(%b) memOrG(%b) " ~ + "shouldLog(%b) %s != %s", gll, + lineCall, ll, ll2, cond, condValue, + memOrG, shouldLog, mem.msg, valueStr + )); + } + } + } + } + } + } + } + } + } +} + +// more testing +@safe unittest +{ + auto oldunspecificLogger = sharedLog; + + auto mem = new TestLogger; + mem.fatalHandler = delegate() {}; + sharedLog = mem; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + int value = 0; + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + mem.logLevel = ll; + + foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + stdThreadLocalLog.logLevel = tll; + + foreach (cond; [true, false]) + { + foreach (condValue; [true, false]) + { + foreach (memOrG; [true, false]) + { + foreach (prntf; [true, false]) + { + foreach (singleMulti; 0 .. 2) + { + int lineCall; + mem.msg = "-1"; + if (memOrG) + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + mem.logf(condValue, "%s", + value); + lineCall = __LINE__; + } + else + { + mem.logf(condValue, + "%d %d", value, value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.logf("%s", value); + lineCall = __LINE__; + } + else + { + mem.logf("%d %d", + value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + mem.log(condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(condValue, + to!string(value), value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + mem.log(to!string(value)); + lineCall = __LINE__; + } + else + { + mem.log(to!string(value), + value); + lineCall = __LINE__; + } + } + } + } + else + { + if (prntf) + { + if (cond) + { + if (singleMulti == 0) + { + logf(condValue, "%s", value); + lineCall = __LINE__; + } + else + { + logf(condValue, "%s %d", value, + value); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + logf("%s", value); + lineCall = __LINE__; + } + else + { + logf("%s %s", value, value); + lineCall = __LINE__; + } + } + } + else + { + if (cond) + { + if (singleMulti == 0) + { + log(condValue, + to!string(value)); + lineCall = __LINE__; + } + else + { + log(condValue, value, + to!string(value)); + lineCall = __LINE__; + } + } + else + { + if (singleMulti == 0) + { + log(to!string(value)); + lineCall = __LINE__; + } + else + { + log(value, to!string(value)); + lineCall = __LINE__; + } + } + } + } + + string valueStr = to!string(value); + ++value; + + bool gllOff = (gll != LogLevel.off); + bool llOff = (ll != LogLevel.off); + bool tllOff = (tll != LogLevel.off); + bool llVSgll = (ll >= gll); + bool tllVSll = + (stdThreadLocalLog.logLevel >= ll); + bool condFalse = (cond ? condValue : true); + + bool shouldLog = gllOff && llOff + && (memOrG ? true : tllOff) + && (memOrG ? + (ll >= gll) : + (tll >= gll && tll >= ll)) + && condFalse; + + if (shouldLog) + { + assert(mem.msg.indexOf(valueStr) != -1, + format("\ngll(%s) ll(%s) tll(%s) " ~ + "cond(%s) condValue(%s) " ~ + "memOrG(%s) prntf(%s) " ~ + "singleMulti(%s)", + gll, ll, tll, cond, condValue, + memOrG, prntf, singleMulti) + ~ format(" gllOff(%s) llOff(%s) " ~ + "llVSgll(%s) tllVSll(%s) " ~ + "tllOff(%s) condFalse(%s) " + ~ "shoudlLog(%s)", + gll != LogLevel.off, + ll != LogLevel.off, llVSgll, + tllVSll, tllOff, condFalse, + shouldLog) + ~ format("msg(%s) line(%s) " ~ + "lineCall(%s) valueStr(%s)", + mem.msg, mem.line, lineCall, + valueStr) + ); + } + else + { + assert(mem.msg.indexOf(valueStr) == -1, + format("\ngll(%s) ll(%s) tll(%s) " ~ + "cond(%s) condValue(%s) " ~ + "memOrG(%s) prntf(%s) " ~ + "singleMulti(%s)", + gll, ll, tll, cond, condValue, + memOrG, prntf, singleMulti) + ~ format(" gllOff(%s) llOff(%s) " ~ + "llVSgll(%s) tllVSll(%s) " ~ + "tllOff(%s) condFalse(%s) " + ~ "shoudlLog(%s)", + gll != LogLevel.off, + ll != LogLevel.off, llVSgll, + tllVSll, tllOff, condFalse, + shouldLog) + ~ format("msg(%s) line(%s) " ~ + "lineCall(%s) valueStr(%s)", + mem.msg, mem.line, lineCall, + valueStr) + ); + } + } + } + } + } + } + } + } + } +} + +// testing more possible log conditions +@safe unittest +{ + bool fatalLog; + auto mem = new TestLogger; + mem.fatalHandler = delegate() { fatalLog = true; }; + auto oldunspecificLogger = sharedLog; + + stdThreadLocalLog.logLevel = LogLevel.all; + + sharedLog = mem; + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + foreach (gll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + + globalLogLevel = gll; + + foreach (ll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + mem.logLevel = ll; + + foreach (tll; [cast(LogLevel) LogLevel.all, LogLevel.trace, + LogLevel.info, LogLevel.warning, LogLevel.error, + LogLevel.critical, LogLevel.fatal, LogLevel.off]) + { + stdThreadLocalLog.logLevel = tll; + + foreach (cond; [true, false]) + { + assert(globalLogLevel == gll); + assert(mem.logLevel == ll); + + bool gllVSll = LogLevel.trace >= globalLogLevel; + bool llVSgll = ll >= globalLogLevel; + bool lVSll = LogLevel.trace >= ll; + bool gllOff = globalLogLevel != LogLevel.off; + bool llOff = mem.logLevel != LogLevel.off; + bool tllOff = stdThreadLocalLog.logLevel != LogLevel.off; + bool tllVSll = tll >= ll; + bool tllVSgll = tll >= gll; + bool lVSgll = LogLevel.trace >= tll; + + bool test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + bool testG = gllOff && llOff && tllOff && lVSgll && tllVSll && tllVSgll && cond; + + mem.line = -1; + /* + writefln("gll(%3u) ll(%3u) cond(%b) test(%b)", + gll, ll, cond, test); + writefln("%b %b %b %b %b %b test2(%b)", llVSgll, gllVSll, lVSll, + gllOff, llOff, cond, test2); + */ + + mem.trace(__LINE__); int line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + trace(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.trace(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + trace(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.tracef("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + tracef("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.tracef(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + tracef(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.info >= ll; + lVSgll = LogLevel.info >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.info(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + info(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.info(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + info(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.infof("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + infof("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.infof(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + infof(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.warning >= ll; + lVSgll = LogLevel.warning >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.warning(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warning(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warning(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warning(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warningf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warningf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.warningf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + warningf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.critical >= ll; + lVSgll = LogLevel.critical >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.critical(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + critical(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.critical(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + critical(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.criticalf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + criticalf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + mem.criticalf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + + criticalf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + + llVSgll = ll >= globalLogLevel; + lVSll = LogLevel.fatal >= ll; + lVSgll = LogLevel.fatal >= tll; + test = llVSgll && gllVSll && lVSll && gllOff && llOff && cond; + testG = gllOff && llOff && tllOff && tllVSll && tllVSgll && + lVSgll && cond; + + mem.fatal(__LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatal(__LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatal(cond, __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatal(cond, __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatalf("%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatalf("%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + + mem.fatalf(cond, "%d", __LINE__); line = __LINE__; + assert(test ? mem.line == line : true); line = -1; + assert(test ? fatalLog : true); + fatalLog = false; + + fatalf(cond, "%d", __LINE__); line = __LINE__; + assert(testG ? mem.line == line : true); line = -1; + assert(testG ? fatalLog : true); + fatalLog = false; + } + } + } + } +} + +// Issue #5 +@safe unittest +{ + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + auto tl = new TestLogger(LogLevel.info); + sharedLog = tl; + + trace("trace"); + assert(tl.msg.indexOf("trace") == -1); +} + +// Issue #5 +@safe unittest +{ + import std.experimental.logger.multilogger : MultiLogger; + stdThreadLocalLog.logLevel = LogLevel.all; + + auto oldunspecificLogger = sharedLog; + + scope(exit) + { + sharedLog = oldunspecificLogger; + globalLogLevel = LogLevel.all; + } + + auto logger = new MultiLogger(LogLevel.error); + + auto tl = new TestLogger(LogLevel.info); + logger.insertLogger("required", tl); + sharedLog = logger; + + trace("trace"); + assert(tl.msg.indexOf("trace") == -1); + info("info"); + assert(tl.msg.indexOf("info") == -1); + error("error"); + assert(tl.msg.indexOf("error") == 0); +} + +unittest +{ + import std.exception : assertThrown; + auto tl = new TestLogger(); + assertThrown!Throwable(tl.fatal("fatal")); +} + +// log objects with non-safe toString +unittest +{ + struct Test + { + string toString() const @system + { + return "test"; + } + } + + auto tl = new TestLogger(); + tl.info(Test.init); + assert(tl.msg == "test"); +} + +// Workaround for atomics not allowed in @safe code +private auto trustedLoad(T)(ref shared T value) @trusted +{ + return atomicLoad!(MemoryOrder.acq)(value); +} + +// ditto +private void trustedStore(T)(ref shared T dst, ref T src) @trusted +{ + atomicStore!(MemoryOrder.rel)(dst, src); +} + +// check that thread-local logging does not propagate +// to shared logger +unittest +{ + import std.concurrency, core.atomic, core.thread; + + static shared logged_count = 0; + + class TestLog : Logger + { + Tid tid; + + this() + { + super (LogLevel.trace); + this.tid = thisTid; + } + + override void writeLogMsg(ref LogEntry payload) @trusted + { + assert(thisTid == this.tid); + atomicOp!"+="(logged_count, 1); + } + } + + class IgnoredLog : Logger + { + this() + { + super (LogLevel.trace); + } + + override void writeLogMsg(ref LogEntry payload) @trusted + { + assert(false); + } + } + + auto oldSharedLog = sharedLog; + scope(exit) + { + sharedLog = oldSharedLog; + } + + sharedLog = new IgnoredLog; + Thread[] spawned; + + foreach (i; 0..4) + { + spawned ~= new Thread({ + stdThreadLocalLog = new TestLog; + trace("zzzzzzzzzz"); + }); + spawned[$-1].start(); + } + + foreach (t; spawned) + t.join(); + + assert (atomicOp!"=="(logged_count, 4)); +} + +@safe unittest +{ + auto dl = cast(FileLogger)sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger)stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/std/experimental/logger/filelogger.d b/std/experimental/logger/filelogger.d new file mode 100644 index 00000000000..270e962f475 --- /dev/null +++ b/std/experimental/logger/filelogger.d @@ -0,0 +1,202 @@ +module std.experimental.logger.filelogger; + +import std.stdio; +import std.experimental.logger.core; + +/** This $(D Logger) implementation writes log messages to the associated +file. The name of the file has to be passed on construction time. If the file +is already present new log messages will be append at its end. +*/ +class FileLogger : Logger +{ + import std.format : formattedWrite; + import std.datetime : SysTime; + import std.concurrency : Tid; + + /** A constructor for the $(D FileLogger) Logger. + + Params: + fn = The filename of the output file of the $(D FileLogger). If that + file can not be opened for writting an exception will be thrown. + lv = The $(D LogLevel) for the $(D FileLogger). By default the + $(D LogLevel) for $(D FileLogger) is $(D LogLevel.info). + + Example: + ------------- + auto l1 = new FileLogger("logFile", "loggerName"); + auto l2 = new FileLogger("logFile", "loggerName", LogLevel.fatal); + ------------- + */ + this(in string fn, const LogLevel lv = LogLevel.info) @safe + { + import std.exception : enforce; + super(lv); + this.filename = fn; + this.file_.open(this.filename, "a"); + } + + /** A constructor for the $(D FileLogger) Logger that takes a reference to + a $(D File). + + The $(D File) passed must be open for all the log call to the + $(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger) + for logging will result in undefined behaviour. + + Params: + file = The file used for logging. + lv = The $(D LogLevel) for the $(D FileLogger). By default the + $(D LogLevel) for $(D FileLogger) is $(D LogLevel.info). + + Example: + ------------- + auto file = File("logFile.log", "w"); + auto l1 = new FileLogger(file, "LoggerName"); + auto l2 = new FileLogger(file, "LoggerName", LogLevel.fatal); + ------------- + */ + this(File file, const LogLevel lv = LogLevel.info) @safe + { + super(lv); + this.file_ = file; + } + + /** If the $(D FileLogger) is managing the $(D File) it logs to, this + method will return a reference to this File. + */ + @property File file() @safe + { + return this.file_; + } + + /* This method overrides the base class method in order to log to a file + without requiring heap allocated memory. Additionally, the $(D FileLogger) + local mutex is logged to serialize the log calls. + */ + override protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + import std.string : lastIndexOf; + ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; + ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; + + auto lt = this.file_.lockingTextWriter(); + systimeToISOString(lt, timestamp); + formattedWrite(lt, ":%s:%s:%u ", file[fnIdx .. $], + funcName[funIdx .. $], line); + } + + /* This methods overrides the base class method and writes the parts of + the log call directly to the file. + */ + override protected void logMsgPart(const(char)[] msg) + { + formattedWrite(this.file_.lockingTextWriter(), "%s", msg); + } + + /* This methods overrides the base class method and finalizes the active + log call. This requires flushing the $(D File) and releasing the + $(D FileLogger) local mutex. + */ + override protected void finishLogMsg() + { + this.file_.lockingTextWriter().put("\n"); + this.file_.flush(); + } + + /* This methods overrides the base class method and delegates the + $(D LogEntry) data to the actual implementation. + */ + override protected void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + + /** If the $(D FileLogger) was constructed with a filename, this method + returns this filename. Otherwise an empty $(D string) is returned. + */ + string getFilename() + { + return this.filename; + } + + private File file_; + private string filename; +} + +unittest +{ + import std.file : remove; + import std.array : empty; + import std.string : indexOf; + + string filename = randomString(32) ~ ".tempLogFile"; + auto l = new FileLogger(filename); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + destroy(l); + + auto file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); +} + +unittest +{ + import std.file : remove; + import std.array : empty; + import std.string : indexOf; + + string filename = randomString(32) ~ ".tempLogFile"; + auto file = File(filename, "w"); + auto l = new FileLogger(file); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + file.close(); + + file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); + file.close(); +} + +@safe unittest +{ + auto dl = cast(FileLogger)sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger)stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/std/experimental/logger/multilogger.d b/std/experimental/logger/multilogger.d new file mode 100644 index 00000000000..18ec7213dcf --- /dev/null +++ b/std/experimental/logger/multilogger.d @@ -0,0 +1,195 @@ +module std.experimental.logger.multilogger; + +import std.experimental.logger.core; +import std.experimental.logger.filelogger; + +/** This Element is stored inside the $(D MultiLogger) and associates a +$(D Logger) to a $(D string). +*/ +struct MultiLoggerEntry +{ + string name; /// The name if the $(D Logger) + Logger logger; /// The stored $(D Logger) +} + +/** MultiLogger logs to multiple $(D Logger). The $(D Logger)s are stored in an +$(D Logger[]) in there order of insertion. + +Every data logged to this $(D MultiLogger) will be distributed to all the $(D +Logger)s inserted into inserted it. This $(D MultiLogger) implementation can +hold multiple $(D Logger)s with the same name. If the method $(D removeLogger) +is used to remove a $(D Logger) only the first occurrence with that name will +be removed. +*/ +class MultiLogger : Logger +{ + /** A constructor for the $(D MultiLogger) Logger. + + Params: + lv = The $(D LogLevel) for the $(D MultiLogger). By default the + $(D LogLevel) for $(D MultiLogger) is $(D LogLevel.info). + + Example: + ------------- + auto l1 = new MultiLogger(LogLevel.trace); + ------------- + */ + this(const LogLevel lv = LogLevel.info) @safe + { + super(lv); + } + + /** This member holds all $(D Logger) stored in the $(D MultiLogger). + + When inheriting from $(D MultiLogger) this member can be used to gain + access to the stored $(D Logger). + */ + protected MultiLoggerEntry[] logger; + + /** This method inserts a new Logger into the $(D MultiLogger). + + Params: + name = The name of the $(D Logger) to insert. + newLogger = The $(D Logger) to insert. + */ + void insertLogger(string name, Logger newLogger) @safe + { + this.logger ~= MultiLoggerEntry(name, newLogger); + } + + /** This method removes a Logger from the $(D MultiLogger). + + Params: + toRemove = The name of the $(D Logger) to remove. If the $(D Logger) + is not found $(D null) will be returned. Only the first occurrence of + a $(D Logger) with the given name will be removed. + + Returns: The removed $(D Logger). + */ + Logger removeLogger(in char[] toRemove) @safe + { + import std.algorithm : copy; + import std.range.interfaces : back, popBack; + for (size_t i = 0; i < this.logger.length; ++i) + { + if (this.logger[i].name == toRemove) + { + Logger ret = this.logger[i].logger; + this.logger[i] = this.logger.back(); + this.logger.popBack(); + + return ret; + } + } + + return null; + } + + /* The override to pass the payload to all children of the + $(D MultiLoggerBase). + */ + override protected void writeLogMsg(ref LogEntry payload) @safe + { + foreach (it; this.logger) + { + /* We don't perform any checks here to avoid race conditions. + Instead the child will check on its own if its log level matches + and assume LogLevel.all for the globalLogLevel (since we already + know the message passes this test). + */ + it.logger.forwardMsg(payload); + } + } +} + +@safe unittest +{ + import std.experimental.logger.nulllogger; + import std.exception : assertThrown; + auto a = new MultiLogger; + auto n0 = new NullLogger(); + auto n1 = new NullLogger(); + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + auto n0_1 = a.removeLogger("zero"); + assert(n0_1 is n0); + auto n = a.removeLogger("zero"); + assert(n is null); + + auto n1_1 = a.removeLogger("one"); + assert(n1_1 is n1); + n = a.removeLogger("one"); + assert(n is null); +} + +@safe unittest +{ + auto a = new MultiLogger; + auto n0 = new TestLogger; + auto n1 = new TestLogger; + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + a.log("Hello TestLogger"); int line = __LINE__; + assert(n0.msg == "Hello TestLogger"); + assert(n0.line == line); + assert(n1.msg == "Hello TestLogger"); + assert(n0.line == line); +} + +// Issue #16 +unittest +{ + import std.stdio : File; + import std.string : indexOf; + auto logName = randomString(32) ~ ".log"; + auto logFileOutput = File(logName, "w"); + scope(exit) + { + import std.file : remove; + logFileOutput.close(); + remove(logName); + } + auto traceLog = new FileLogger(logFileOutput, LogLevel.all); + auto infoLog = new TestLogger(LogLevel.info); + + auto root = new MultiLogger(LogLevel.all); + root.insertLogger("fileLogger", traceLog); + root.insertLogger("stdoutLogger", infoLog); + + string tMsg = "A trace message"; + root.trace(tMsg); int line1 = __LINE__; + + assert(infoLog.line != line1); + assert(infoLog.msg != tMsg); + + string iMsg = "A info message"; + root.info(iMsg); int line2 = __LINE__; + + assert(infoLog.line == line2); + assert(infoLog.msg == iMsg, infoLog.msg ~ ":" ~ iMsg); + + logFileOutput.close(); + logFileOutput = File(logName, "r"); + assert(logFileOutput.isOpen); + assert(!logFileOutput.eof); + + auto line = logFileOutput.readln(); + assert(line.indexOf(tMsg) != -1, line ~ ":" ~ tMsg); + assert(!logFileOutput.eof); + line = logFileOutput.readln(); + assert(line.indexOf(iMsg) != -1, line ~ ":" ~ tMsg); +} + +@safe unittest +{ + auto dl = cast(FileLogger)sharedLog; + assert(dl !is null); + assert(dl.logLevel == LogLevel.all); + assert(globalLogLevel == LogLevel.all); + + auto tl = cast(StdForwardLogger)stdThreadLocalLog; + assert(tl !is null); + stdThreadLocalLog.logLevel = LogLevel.all; +} diff --git a/std/experimental/logger/nulllogger.d b/std/experimental/logger/nulllogger.d new file mode 100644 index 00000000000..bfa7981144e --- /dev/null +++ b/std/experimental/logger/nulllogger.d @@ -0,0 +1,36 @@ +module std.experimental.logger.nulllogger; + +import std.experimental.logger.core; + +/** The $(D NullLogger) will not process any log messages. + +In case of a log message with $(D LogLevel.fatal) nothing will happen. +*/ +class NullLogger : Logger +{ + /** The default constructor for the $(D NullLogger). + + Independent of the parameter this Logger will never log a message. + + Params: + lv = The $(D LogLevel) for the $(D NullLogger). By default the $(D LogLevel) + for $(D NullLogger) is $(D LogLevel.info). + */ + this(const LogLevel lv = LogLevel.info) @safe + { + super(lv); + this.fatalHandler = delegate() {}; + } + + override protected void writeLogMsg(ref LogEntry payload) @safe @nogc + { + } +} + +/// +@safe unittest +{ + auto nl1 = new NullLogger(LogLevel.all); + nl1.info("You will never read this."); + nl1.fatal("You will never read this, either and it will not throw"); +} diff --git a/std/experimental/logger/package.d b/std/experimental/logger/package.d new file mode 100644 index 00000000000..f45320a7c3d --- /dev/null +++ b/std/experimental/logger/package.d @@ -0,0 +1,150 @@ +/** +Implements logging facilities. + +Message logging is a common approach to expose runtime information of a +program. Logging should be easy, but also flexible and powerful, therefore +$(D D) provides a standard interface for logging. + +The easiest way to create a log message is to write +$(D import std.logger; log("I am here");) this will print a message to the +$(D stderr) device. The message will contain the filename, the linenumber, the +name of the surrounding function, the time and the message. + +Copyright: Copyright Robert "burner" Schadek 2013 -- +License: Boost License 1.0. +Authors: $(WEB http://www.svs.informatik.uni-oldenburg.de/60865.html, Robert burner Schadek) + +------------- +log("Logging to the sharedLog with its default LogLevel"); +logf(LogLevel.info, 5 < 6, "%s to the sharedLog with its LogLevel.info", "Logging"); +info("Logging to the sharedLog with its info LogLevel"); +warning(5 < 6, "Logging to the sharedLog with its LogLevel.warning if 5 is less than 6"); +error("Logging to the sharedLog with its error LogLevel"); +errorf("Logging %s the sharedLog %s its error LogLevel", "to", "with"); +critical("Logging to the"," sharedLog with its error LogLevel"); +fatal("Logging to the sharedLog with its fatal LogLevel"); + +auto fLogger = new FileLogger("NameOfTheLogFile"); +fLogger.log("Logging to the fileLogger with its default LogLevel"); +fLogger.info("Logging to the fileLogger with its default LogLevel"); +fLogger.warning(5 < 6, "Logging to the fileLogger with its LogLevel.warning if 5 is less than 6"); +fLogger.warningf(5 < 6, "Logging to the fileLogger with its LogLevel.warning if %s is %s than 6", 5, "less"); +fLogger.critical("Logging to the fileLogger with its info LogLevel"); +fLogger.log(LogLevel.trace, 5 < 6, "Logging to the fileLogger"," with its default LogLevel if 5 is less than 6"); +fLogger.fatal("Logging to the fileLogger with its warning LogLevel"); +------------- + +Top-level calls to logging-related functions go to the default $(D Logger) +object called $(D sharedLog). +$(LI $(D log)) +$(LI $(D trace)) +$(LI $(D info)) +$(LI $(D warning)) +$(LI $(D critical)) +$(LI $(D fatal)) +The default $(D Logger) will by default log to $(D stderr) and has a default +$(D LogLevel) of $(D LogLevel.all). The default Logger can be accessed by +using the property called $(D sharedLog). This property a reference to the +current default $(D Logger). This reference can be used to assign a new +default $(D Logger). +------------- +sharedLog = new FileLogger("New_Default_Log_File.log"); +------------- + +Additional $(D Logger) can be created by creating a new instance of the +required $(D Logger). + +The $(D LogLevel) of an log call can be defined in two ways. The first is by +calling $(D log) and passing the $(D LogLevel) explicit as the first argument. +The second way of setting the $(D LogLevel) of a +log call, is by calling either $(D trace), $(D info), $(D warning), +$(D critical), or $(D fatal). The log call will than have the respective +$(D LogLevel). If no $(D LogLevel) is defined the log call will use the +current $(D LogLevel) of the used $(D Logger). If data is logged with +$(D LogLevel) $(D fatal) by default an $(D Error) will be thrown. +This behaviour can be modified by using the member $(D fatalHandler) to +assign a custom delegate to handle log call with $(D LogLevel) $(D fatal). + +Conditional logging can be achieved be appending passing a $(D bool) as first +argument to a log function. If conditional logging is used the condition must +be $(D true) in order to have the log message logged. + +In order to combine an explicit $(D LogLevel) passing with conditional +logging, the $(D LogLevel) has to be passed as first argument followed by the +$(D bool). + +Messages are logged if the $(D LogLevel) of the log message is greater than or +equal to than the $(D LogLevel) of the used $(D Logger) and additionally if the +$(D LogLevel) of the log message is greater equal to the global $(D LogLevel). +If a condition is passed into the log call, this condition must be true. + +The global $(D LogLevel) is accessible by using $(D globalLogLevel). +To assign the $(D LogLevel) of a $(D Logger) use the $(D logLevel) property of +the logger. + +If $(D printf)-style logging is needed add a $(B f) to the logging call, such as +$(D myLogger.infof("Hello %s", "world");) or $(fatalf("errno %d", 1337)) +The additional $(B f) enables $(D printf)-style logging for call combinations of +explicit $(D LogLevel) and conditional logging functions and methods. + +To customize the $(D Logger) behavior, create a new $(D class) that inherits from +the abstract $(D Logger) $(D class), and implements the $(D writeLogMsg) +method. +------------- +class MyCustomLogger : Logger +{ + this(string newName, LogLevel lv) @safe + { + super(newName, lv); + } + + override void writeLogMsg(ref LogEntry payload) + { + // log message in my custom way + } +} + +auto logger = new MyCustomLogger(); +logger.log("Awesome log message"); +------------- + +To gain more precise control over the logging process, additionally to +overwriting the $(D writeLogMsg) method the methods $(D beginLogMsg), +$(D logMsgPart) and $(D finishLogMsg) can be overwritten. + +In order to disable logging at compile time, pass $(D StdLoggerDisableLogging) as a +version argument to the $(D D) compiler when compiling your program code. +This will disable all logging functionality. +Specific $(D LogLevel) can be disabled at compile time as well. +In order to disable logging with the $(D trace) $(D LogLevel) pass +$(D StdLoggerDisableTrace) as a version. +The following table shows which version statement disables which +$(D LogLevel). +$(TABLE + $(TR $(TD $(D LogLevel.trace) ) $(TD StdLoggerDisableTrace)) + $(TR $(TD $(D LogLevel.info) ) $(TD StdLoggerDisableInfo)) + $(TR $(TD $(D LogLevel.warning) ) $(TD StdLoggerDisableWarning)) + $(TR $(TD $(D LogLevel.error) ) $(TD StdLoggerDisableError)) + $(TR $(TD $(D LogLevel.critical) ) $(TD StdLoggerDisableCritical)) + $(TR $(TD $(D LogLevel.fatal) ) $(TD StdLoggerDisableFatal)) +) +Such a version statement will only disable logging in the associated compile +unit. + +By default four $(D Logger) implementations are given. The $(D FileLogger) +logs data to files. It can also be used to log to $(D stdout) and $(D stderr) +as these devices are files as well. A $(D Logger) that logs to $(D stdout) can +therefore be created by $(D new FileLogger(stdout)). +The $(D MultiLogger) is basically an associative array of $(D string)s to +$(D Logger). It propagates log calls to its stored $(D Logger). The +$(D ArrayLogger) contains an array of $(D Logger) and also propagates log +calls to its stored $(D Logger). The $(D NullLogger) does not do anything. It +will never log a message and will never throw on a log call with $(D LogLevel) +$(D error). +*/ +module std.experimental.logger; + +public import std.experimental.logger.core; +public import std.experimental.logger.filelogger; +public import std.experimental.logger.nulllogger; +public import std.experimental.logger.multilogger; diff --git a/win32.mak b/win32.mak index ce21b2723da..4a100da5509 100644 --- a/win32.mak +++ b/win32.mak @@ -129,9 +129,13 @@ SRC_STD_DIGEST= std\digest\crc.d std\digest\sha.d std\digest\md.d \ std\digest\ripemd.d std\digest\digest.d SRC_STD_CONTAINER= std\container\array.d std\container\binaryheap.d \ - std\container\dlist.d std\container\rbtree.d std\container\slist.d \ - std\container\util.d std\container\package.d + std\container\dlist.d std\container\rbtree.d std\container\slist.d \ + std\container\util.d std\container\package.d +SRC_STD_LOGGER= std\experimental\logger\core.d std\experimental\logger\filelogger.d \ + std\experimental\logger\multilogger.d std\experimental\logger\nulllogger.d \ + std\experimental\logger\package.d + SRC_STD_4= std\uuid.d $(SRC_STD_DIGEST) SRC_STD_ALGO= std\algorithm\package.d std\algorithm\comparison.d \ @@ -145,13 +149,13 @@ SRC_STD_6= std\variant.d \ std\syserror.d std\zlib.d \ std\stream.d std\socket.d std\socketstream.d \ std\conv.d std\zip.d std\cstream.d \ - $(SRC_STD_CONTAINER) + $(SRC_STD_CONTAINER) $(SRC_STD_LOGGER) SRC_STD_REST= std\stdint.d \ std\json.d \ std\parallelism.d \ std\mathspecial.d \ - std\process.d + std\process.d \ SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2a_HEAVY) \ $(SRC_STD_3) $(SRC_STD_3a) $(SRC_STD_3b) $(SRC_STD_4) \ @@ -371,6 +375,11 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_zlib.html \ $(DOC)\std_net_isemail.html \ $(DOC)\std_net_curl.html \ + $(DOC)\std_experimental_logger_core.html \ + $(DOC)\std_experimental_logger_filelogger.html \ + $(DOC)\std_experimental_logger_multilogger.html \ + $(DOC)\std_experimental_logger_nulllogger.html \ + $(DOC)\std_experimental_logger_package.html \ $(DOC)\std_windows_charset.html \ $(DOC)\std_windows_registry.html \ $(DOC)\std_c_fenv.html \ @@ -785,6 +794,21 @@ $(DOC)\std_net_isemail.html : $(STDDOC) std\net\isemail.d $(DOC)\std_net_curl.html : $(STDDOC) std\net\curl.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_net_curl.html $(STDDOC) std\net\curl.d +$(DOC)\std_experimental_logger_core.html : $(STDDOC) std\experimental\logger\core.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_core.html $(STDDOC) std\experimental\logger\core.d + +$(DOC)\std_experimental_logger_multilogger.html : $(STDDOC) std\experimental\logger\multilogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_multilogger.html $(STDDOC) std\experimental\logger\multilogger.d + +$(DOC)\std_experimental_logger_filelogger.html : $(STDDOC) std\experimental\logger\filelogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_filelogger.html $(STDDOC) std\experimental\logger\filelogger.d + +$(DOC)\std_experimental_logger_nulllogger.html : $(STDDOC) std\experimental\logger\nulllogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_nulllogger.html $(STDDOC) std\experimental\logger\nulllogger.d + +$(DOC)\std_experimental_logger_package.html : $(STDDOC) std\experimental\logger\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_package.html $(STDDOC) std\experimental\logger\package.d + $(DOC)\std_digest_crc.html : $(STDDOC) std\digest\crc.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_digest_crc.html $(STDDOC) std\digest\crc.d @@ -856,7 +880,8 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ $(SRC_STD_C_WIN) $(SRC_STD_C_LINUX) $(SRC_STD_C_OSX) $(SRC_STD_C_FREEBSD) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ - $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) + $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ + $(SRC_STD_LOGGER) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak $(STDDOC) zip32 -u phobos $(SRC) @@ -879,6 +904,7 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_STD_REGEX) zip32 -u phobos $(SRC_STD_RANGE) zip32 -u phobos $(SRC_STD_ALGO) + zip32 -u phobos $(SRC_STD_LOGGER) phobos.zip : zip diff --git a/win64.mak b/win64.mak index a8311d22ca1..af44169e375 100644 --- a/win64.mak +++ b/win64.mak @@ -171,7 +171,8 @@ SRC_STD_ALL= $(SRC_STD_1_HEAVY) $(SRC_STD_2a_HEAVY) \ $(SRC_STD_6h) \ $(SRC_STD_6i) \ $(SRC_STD_6j) \ - $(SRC_STD_7) + $(SRC_STD_7) \ + $(SRC_STD_LOGGER) SRC= unittest.d index.d @@ -203,6 +204,10 @@ SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ SRC_STD_NET= std\net\isemail.d std\net\curl.d +SRC_STD_LOGGER= std\experimental\logger\core.d std\experimental\logger\filelogger.d \ + std\experimental\logger\multilogger.d std\experimental\logger\nulllogger.d \ + std\experimental\logger\package.d + SRC_STD_C= std\c\process.d std\c\stdlib.d std\c\time.d std\c\stdio.d \ std\c\math.d std\c\stdarg.d std\c\stddef.d std\c\fenv.d std\c\string.d \ std\c\locale.d std\c\wcharh.d @@ -387,6 +392,11 @@ DOCS= $(DOC)\object.html \ $(DOC)\std_zlib.html \ $(DOC)\std_net_isemail.html \ $(DOC)\std_net_curl.html \ + $(DOC)\std_experimental_logger_filelogger.html \ + $(DOC)\std_experimental_logger_multilogger.html \ + $(DOC)\std_experimental_logger_nulllogger.html \ + $(DOC)\std_experimental_logger_core.html \ + $(DOC)\std_experimental_logger_package.html \ $(DOC)\std_windows_charset.html \ $(DOC)\std_windows_registry.html \ $(DOC)\std_c_fenv.html \ @@ -748,6 +758,21 @@ $(DOC)\std_net_isemail.html : $(STDDOC) std\net\isemail.d $(DOC)\std_net_curl.html : $(STDDOC) std\net\curl.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_net_curl.html $(STDDOC) std\net\curl.d +$(DOC)\std_experimental_logger_core.html : $(STDDOC) std\experimental\logger\core.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_core.html $(STDDOC) std\experimental\logger\core.d + +$(DOC)\std_experimental_logger_multilogger.html : $(STDDOC) std\experimental\logger\multilogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_multilogger.html $(STDDOC) std\experimental\logger\multilogger.d + +$(DOC)\std_experimental_logger_filelogger.html : $(STDDOC) std\experimental\logger\filelogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_filelogger.html $(STDDOC) std\experimental\logger\filelogger.d + +$(DOC)\std_experimental_logger_nulllogger.html : $(STDDOC) std\experimental\logger\nulllogger.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_nulllogger.html $(STDDOC) std\experimental\logger\nulllogger.d + +$(DOC)\std_experimental_logger_package.html : $(STDDOC) std\experimental\logger\package.d + $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_experimental_logger_package.html $(STDDOC) std\experimental\logger\package.d + $(DOC)\std_digest_crc.html : $(STDDOC) std\digest\crc.d $(DMD) -c -o- $(DDOCFLAGS) -Df$(DOC)\std_digest_crc.html $(STDDOC) std\digest\crc.d @@ -819,7 +844,8 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ $(SRC_STD_C_WIN) $(SRC_STD_C_LINUX) $(SRC_STD_C_OSX) $(SRC_STD_C_FREEBSD) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ - $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) + $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ + $(SRC_STD_LOGGER) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak $(STDDOC) zip32 -u phobos $(SRC) @@ -837,6 +863,7 @@ zip : win32.mak win64.mak posix.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_ETC) $(SRC_ETC_C) zip32 -u phobos $(SRC_ZLIB) zip32 -u phobos $(SRC_STD_NET) + zip32 -u phobos $(SRC_STD_LOGGER) zip32 -u phobos $(SRC_STD_DIGEST) zip32 -u phobos $(SRC_STD_CONTAINER) zip32 -u phobos $(SRC_STD_REGEX)