mirror of
https://github.com/dlang/phobos.git
synced 2025-04-27 13:40:20 +03:00
3060 lines
103 KiB
D
3060 lines
103 KiB
D
// Written in the D programming language.
|
|
/**
|
|
Source: $(PHOBOSSRC std/logger/core.d)
|
|
*/
|
|
module std.logger.core;
|
|
|
|
import core.atomic : atomicLoad, atomicOp, atomicStore, MemoryOrder;
|
|
import core.sync.mutex : Mutex;
|
|
import std.datetime.date : DateTime;
|
|
import std.datetime.systime : Clock, SysTime;
|
|
import std.range.primitives;
|
|
import std.traits;
|
|
|
|
import std.logger.filelogger;
|
|
|
|
/** This functions is used at runtime to determine if a `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
|
|
{
|
|
return ll >= globalLL
|
|
&& ll >= loggerLL
|
|
&& ll != LogLevel.off
|
|
&& globalLL != LogLevel.off
|
|
&& loggerLL != LogLevel.off
|
|
&& condition;
|
|
}
|
|
|
|
/* This function formates a `SysTime` into an `OutputRange`.
|
|
|
|
The `SysTime` is formatted similar to
|
|
$(LREF std.datatime.DateTime.toISOExtString) except the fractional second part.
|
|
The fractional second part is in milliseconds and is always 3 digits.
|
|
*/
|
|
void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time)
|
|
if (isOutputRange!(OutputRange,string))
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
const auto dt = cast(DateTime) time;
|
|
const auto fsec = time.fracSecs.total!"msecs";
|
|
|
|
formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d",
|
|
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
|
|
fsec);
|
|
}
|
|
|
|
/** This function logs data.
|
|
|
|
In order for the data to be processed, the `LogLevel` of the log call must
|
|
be greater or equal to the `LogLevel` of the `sharedLog` and the
|
|
`defaultLogLevel`; additionally the condition passed must be `true`.
|
|
|
|
Params:
|
|
ll = The `LogLevel` used by this log call.
|
|
condition = The condition must be `true` for the data to be logged.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
if (args.length != 1)
|
|
{
|
|
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__)
|
|
{
|
|
stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName,
|
|
prettyFuncName);
|
|
}
|
|
|
|
/** This function logs data.
|
|
|
|
In order for the data to be processed the `LogLevel` of the log call must
|
|
be greater or equal to the `LogLevel` of the `sharedLog`.
|
|
|
|
Params:
|
|
ll = The `LogLevel` used by this log call.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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))
|
|
{
|
|
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__)
|
|
{
|
|
stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName,
|
|
moduleName);
|
|
}
|
|
|
|
/** This function logs data.
|
|
|
|
In order for the data to be processed the `LogLevel` of the
|
|
`sharedLog` must be greater or equal to the `defaultLogLevel`
|
|
add the condition passed must be `true`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
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__)
|
|
{
|
|
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 `LogLevel` of the
|
|
`sharedLog` must be greater or equal to the `defaultLogLevel`.
|
|
|
|
Params:
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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))
|
|
|| args.length == 0)
|
|
{
|
|
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__)
|
|
{
|
|
stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file,
|
|
funcName, prettyFuncName, moduleName);
|
|
}
|
|
|
|
/** This function logs data in a `printf`-style manner.
|
|
|
|
In order for the data to be processed the `LogLevel` of the log call must
|
|
be greater or equal to the `LogLevel` of the `sharedLog` and the
|
|
`defaultLogLevel` additionally the condition passed must be `true`.
|
|
|
|
Params:
|
|
ll = The `LogLevel` used by this log call.
|
|
condition = The condition must be `true` for the data to be logged.
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
|
|
(ll, condition, msg, args);
|
|
}
|
|
|
|
/** This function logs data in a `printf`-style manner.
|
|
|
|
In order for the data to be processed the `LogLevel` of the log call must
|
|
be greater or equal to the `LogLevel` of the `sharedLog` and the
|
|
`defaultLogLevel`.
|
|
|
|
Params:
|
|
ll = The `LogLevel` used by this log call.
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
|
|
(ll, msg, args);
|
|
}
|
|
|
|
/** This function logs data in a `printf`-style manner.
|
|
|
|
In order for the data to be processed the `LogLevel` of the log call must
|
|
be greater or equal to the `defaultLogLevel` additionally the condition
|
|
passed must be `true`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName)
|
|
(stdThreadLocalLog.logLevel, condition, msg, args);
|
|
}
|
|
|
|
/** This function logs data in a `printf`-style manner.
|
|
|
|
In order for the data to be processed the `LogLevel` of the log call must
|
|
be greater or equal to the `defaultLogLevel`.
|
|
|
|
Params:
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName)
|
|
(stdThreadLocalLog.logLevel, msg, args);
|
|
}
|
|
|
|
/** This template provides the global log functions with the `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)
|
|
if ((args.length > 0 && !is(Unqual!(A[0]) : bool)) || args.length == 0)
|
|
{
|
|
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)
|
|
{
|
|
stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName,
|
|
prettyFuncName, moduleName)(condition, args);
|
|
}
|
|
}
|
|
|
|
/** This function logs data to the `stdThreadLocalLog`, optionally depending
|
|
on a condition.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel` must
|
|
be greater or equal than the `LogLevel` of the `stdThreadLocalLog` and
|
|
must be greater or equal than the global `LogLevel`.
|
|
Additionally the `LogLevel` must be greater or equal than the `LogLevel`
|
|
of the `stdSharedLogger`.
|
|
If a condition is given, it must evaluate to `true`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
trace(1337, "is number");
|
|
info(1337, "is number");
|
|
error(1337, "is number");
|
|
critical(1337, "is number");
|
|
fatal(1337, "is number");
|
|
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 `printf`-style log functions with
|
|
the `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)
|
|
{
|
|
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)
|
|
{
|
|
stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName,
|
|
prettyFuncName, moduleName)(condition, msg, args);
|
|
}
|
|
}
|
|
|
|
/** This function logs data to the `sharedLog` in a `printf`-style
|
|
manner.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel` must
|
|
be greater or equal than the `LogLevel` of the `sharedLog` and
|
|
must be greater or equal than the global `LogLevel`.
|
|
Additionally the `LogLevel` must be greater or equal than the `LogLevel`
|
|
of the `stdSharedLogger`.
|
|
|
|
Params:
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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 `sharedLog` in a $(D
|
|
printf)-style manner.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel` must
|
|
be greater or equal than the `LogLevel` of the `sharedLog` and
|
|
must be greater or equal than the global `LogLevel`.
|
|
Additionally the `LogLevel` must be greater or equal than the `LogLevel`
|
|
of the `stdSharedLogger`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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;
|
|
|
|
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;
|
|
char[4] buffer;
|
|
size_t len = encode(buffer, elem);
|
|
log.logMsgPart(buffer[0 .. len]);
|
|
}
|
|
}
|
|
|
|
private void formatString(A...)(MsgRange oRange, A args)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
foreach (arg; args)
|
|
{
|
|
formattedWrite(oRange, "%s", arg);
|
|
}
|
|
}
|
|
|
|
@system 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 `LogLevel.fatal` is called the shutdown handler of
|
|
that logger is called.
|
|
*/
|
|
enum LogLevel : ubyte
|
|
{
|
|
all = 1, /** Lowest possible assignable `LogLevel`. */
|
|
trace = 32, /** `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 `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 `writeLogMsg` method. By
|
|
default this is not thread-safe.
|
|
|
|
It is also possible to `override` the three methods `beginLogMsg`,
|
|
`logMsgPart` and `finishLogMsg` together, this option gives more
|
|
flexibility.
|
|
*/
|
|
abstract class Logger
|
|
{
|
|
import std.array : appender, Appender;
|
|
import std.concurrency : thisTid, Tid;
|
|
|
|
/** 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 `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 `Logger` used to create this `LogEntry`
|
|
Logger logger;
|
|
}
|
|
|
|
/**
|
|
Every subclass of `Logger` has to call this constructor from their
|
|
constructor. It sets the `LogLevel`, and creates a fatal handler. The fatal
|
|
handler will throw an `Error` if a log call is made with level
|
|
`LogLevel.fatal`.
|
|
|
|
Params:
|
|
lv = `LogLevel` to use for this `Logger` instance.
|
|
*/
|
|
this(this This)(LogLevel lv)
|
|
{
|
|
this.logLevel_ = lv;
|
|
this.fatalHandler_ = delegate() {
|
|
throw new Error("A fatal log message was logged");
|
|
};
|
|
|
|
this.mutex = new typeof(mutex)();
|
|
}
|
|
|
|
/** A custom logger must implement this method in order to work in a
|
|
`MultiLogger` and `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 `std.array.appender`
|
|
internally to construct the message string. This means dynamic,
|
|
GC memory allocation. A logger can avoid this allocation by
|
|
reimplementing `beginLogMsg`, `logMsgPart` and `finishLogMsg`.
|
|
`beginLogMsg` is always called first, followed by any number of calls
|
|
to `logMsgPart` and one call to `finishLogMsg`.
|
|
|
|
As an example for such a custom `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
|
|
{
|
|
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(scope const(char)[] msg) @safe
|
|
{
|
|
msgAppender.put(msg);
|
|
}
|
|
|
|
/** Signals that the message has been written and no more calls to
|
|
`logMsgPart` follow. */
|
|
protected void finishLogMsg() @safe
|
|
{
|
|
header.msg = msgAppender.data;
|
|
this.writeLogMsg(header);
|
|
}
|
|
|
|
/** The `LogLevel` determines if the log call are processed or dropped
|
|
by the `Logger`. In order for the log call to be processed the
|
|
`LogLevel` of the log call must be greater or equal to the `LogLevel`
|
|
of the `logger`.
|
|
|
|
These two methods set and get the `LogLevel` of the used `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) @safe @nogc
|
|
{
|
|
atomicStore(this.logLevel_, lv);
|
|
}
|
|
|
|
/** This `delegate` is called in case a log message with
|
|
`LogLevel.fatal` gets logged.
|
|
|
|
By default an `Error` will be thrown.
|
|
*/
|
|
@property final void delegate() fatalHandler() @safe @nogc
|
|
{
|
|
synchronized (mutex) return this.fatalHandler_;
|
|
}
|
|
|
|
/// Ditto
|
|
@property final void fatalHandler(void delegate() @safe fh) @safe @nogc
|
|
{
|
|
synchronized (mutex) this.fatalHandler_ = fh;
|
|
}
|
|
|
|
/** This method allows forwarding log entries from one logger to another.
|
|
|
|
`forwardMsg` will ensure proper synchronization and then call
|
|
`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 `globalLogLevel` wont be evaluated again
|
|
since it is assumed that the caller already checked that.
|
|
*/
|
|
void forwardMsg(ref LogEntry payload) @trusted
|
|
{
|
|
if (isLoggingEnabled(payload.logLevel, this.logLevel_,
|
|
globalLogLevel))
|
|
{
|
|
this.writeLogMsg(payload);
|
|
|
|
if (payload.logLevel == LogLevel.fatal)
|
|
this.fatalHandler_();
|
|
}
|
|
}
|
|
|
|
/** This template provides the log functions for the `Logger` `class`
|
|
with the `LogLevel` encoded in the function name.
|
|
|
|
For further information see 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 `Logger`.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
must be greater or equal than the `LogLevel` of the used `Logger`
|
|
and must be greater or equal than the global `LogLevel`.
|
|
|
|
Params:
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)))
|
|
{
|
|
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 `Logger` depending on a
|
|
condition.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel` must
|
|
be greater or equal than the `LogLevel` of the used `Logger` and
|
|
must be greater or equal than the global `LogLevel` additionally the
|
|
condition passed must be `true`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
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 `Logger` in a
|
|
`printf`-style manner.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
must be greater or equal than the `LogLevel` of the used `Logger`
|
|
and must be greater or equal than the global `LogLevel` additionally
|
|
the passed condition must be `true`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
synchronized (mutex)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
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 `Logger` in a
|
|
`printf`-style manner.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel` must
|
|
be greater or equal than the `LogLevel` of the used `Logger` and
|
|
must be greater or equal than the global `LogLevel`.
|
|
|
|
Params:
|
|
msg = The `printf`-style string.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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)
|
|
{
|
|
synchronized (mutex)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
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 `LogLevel` of the used `Logger`.
|
|
|
|
This method takes a `bool` as first argument. In order for the
|
|
data to be processed the `bool` must be `true` and the `LogLevel`
|
|
of the Logger must be greater or equal to the global `LogLevel`.
|
|
|
|
Params:
|
|
args = The data that should be logged.
|
|
condition = The condition must be `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.
|
|
|
|
Example:
|
|
--------------------
|
|
auto l = new StdioLogger();
|
|
l.log(1337);
|
|
--------------------
|
|
*/
|
|
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)
|
|
if (args.length != 1)
|
|
{
|
|
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
|
|
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__)
|
|
{
|
|
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_();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** This function logs data to the used `Logger` with a specific
|
|
`LogLevel`.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
must be greater or equal than the `LogLevel` of the used `Logger`
|
|
and must be greater or equal than the global `LogLevel`.
|
|
|
|
Params:
|
|
ll = The specific `LogLevel` used for logging the log message.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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");
|
|
--------------------
|
|
*/
|
|
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)) || args.length == 0)
|
|
{
|
|
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
|
|
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__)
|
|
{
|
|
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 `Logger` depending on a
|
|
explicitly passed condition with the `LogLevel` of the used
|
|
`Logger`.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
of the used `Logger` must be greater or equal than the global
|
|
`LogLevel` and the condition must be `true`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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");
|
|
--------------------
|
|
*/
|
|
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)
|
|
{
|
|
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
|
|
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__)
|
|
{
|
|
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 `Logger` with the `LogLevel`
|
|
of the used `Logger`.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
of the used `Logger` must be greater or equal than the global
|
|
`LogLevel`.
|
|
|
|
Params:
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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");
|
|
--------------------
|
|
*/
|
|
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(immutable A[0] == immutable LogLevel))
|
|
|| args.length == 0)
|
|
{
|
|
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
|
|
void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__,
|
|
string funcName = __FUNCTION__,
|
|
string prettyFuncName = __PRETTY_FUNCTION__,
|
|
string moduleName = __MODULE__)
|
|
{
|
|
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 `Logger` with a specific
|
|
`LogLevel` and depending on a condition in a `printf`-style manner.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
must be greater or equal than the `LogLevel` of the used `Logger`
|
|
and must be greater or equal than the global `LogLevel` and the
|
|
condition must be `true`.
|
|
|
|
Params:
|
|
ll = The specific `LogLevel` used for logging the log message.
|
|
condition = The condition must be `true` for the data to be logged.
|
|
msg = The format string used for this log call.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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");
|
|
--------------------
|
|
*/
|
|
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)
|
|
{
|
|
synchronized (mutex)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
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 `Logger` with a specific
|
|
`LogLevel` in a `printf`-style manner.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
must be greater or equal than the `LogLevel` of the used `Logger`
|
|
and must be greater or equal than the global `LogLevel`.
|
|
|
|
Params:
|
|
ll = The specific `LogLevel` used for logging the log message.
|
|
msg = The format string used for this log call.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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");
|
|
--------------------
|
|
*/
|
|
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)
|
|
{
|
|
synchronized (mutex)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
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 `Logger` depending on a
|
|
condition with the `LogLevel` of the used `Logger` in a
|
|
`printf`-style manner.
|
|
|
|
In order for the resulting log message to be logged the `LogLevel`
|
|
of the used `Logger` must be greater or equal than the global
|
|
`LogLevel` and the condition must be `true`.
|
|
|
|
Params:
|
|
condition = The condition must be `true` for the data to be logged.
|
|
msg = The format string used for this log call.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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");
|
|
--------------------
|
|
*/
|
|
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)
|
|
{
|
|
synchronized (mutex)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
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);
|
|
formattedWrite(writer, msg, args);
|
|
|
|
this.finishLogMsg();
|
|
|
|
if (this.logLevel_ == LogLevel.fatal)
|
|
this.fatalHandler_();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** This method logs data to the used `Logger` with the `LogLevel`
|
|
of the this `Logger` in a `printf`-style manner.
|
|
|
|
In order for the data to be processed the `LogLevel` of the `Logger`
|
|
must be greater or equal to the global `LogLevel`.
|
|
|
|
Params:
|
|
msg = The format string used for this log call.
|
|
args = The data that should be logged.
|
|
|
|
Example:
|
|
--------------------
|
|
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");
|
|
--------------------
|
|
*/
|
|
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)
|
|
{
|
|
synchronized (mutex)
|
|
{
|
|
import std.format.write : formattedWrite;
|
|
|
|
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 shared 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 shared(Logger) defaultSharedLoggerImpl() @trusted
|
|
{
|
|
import core.lifetime : emplace;
|
|
import std.stdio : stderr;
|
|
|
|
__gshared align(__traits(classInstanceAlignment, FileLogger))
|
|
void[__traits(classInstanceSize, FileLogger)] _buffer = void;
|
|
|
|
import std.concurrency : initOnce;
|
|
initOnce!stdSharedDefaultLogger({
|
|
auto buffer = cast(ubyte[]) _buffer;
|
|
return cast(shared) emplace!(FileLogger)(buffer, stderr, LogLevel.info);
|
|
}());
|
|
|
|
return atomicLoad(stdSharedDefaultLogger);
|
|
}
|
|
|
|
/** This property sets and gets the default `Logger`. Unless set to another
|
|
logger by the user, the default logger's log level is LogLevel.info.
|
|
|
|
Example:
|
|
-------------
|
|
sharedLog = new shared FileLogger(yourFile);
|
|
-------------
|
|
The example sets a new `FileLogger` as new `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 `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 `sharedLog`.
|
|
|
|
`sharedLog` is only thread-safe if the used `Logger` is thread-safe.
|
|
The default `Logger` is thread-safe.
|
|
-------------
|
|
if (sharedLog !is myLogger)
|
|
sharedLog = new shared myLogger;
|
|
-------------
|
|
*/
|
|
@property shared(Logger) sharedLog() @safe
|
|
{
|
|
// If we have set up our own logger use that
|
|
if (auto logger = atomicLoad!(MemoryOrder.seq)(stdSharedLogger))
|
|
{
|
|
return atomicLoad(logger);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise resort to the default logger
|
|
return defaultSharedLoggerImpl;
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
@property void sharedLog(shared(Logger) logger) @safe
|
|
{
|
|
atomicStore!(MemoryOrder.seq)(stdSharedLogger, atomicLoad(logger));
|
|
}
|
|
|
|
/** These methods get and set the global `LogLevel`.
|
|
|
|
Every log message with a `LogLevel` lower than the global `LogLevel`
|
|
will be discarded before it reaches `writeLogMessage` method of any
|
|
`Logger`.
|
|
*/
|
|
/* Implementation note:
|
|
For any public logging call, the global log level shall only be queried once on
|
|
entry. Otherwise when another thread changes the level, we would 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 `StdForwardLogger` will always forward anything to the sharedLog.
|
|
|
|
The `StdForwardLogger` will not throw if data is logged with $(D
|
|
LogLevel.fatal).
|
|
*/
|
|
class StdForwardLogger : Logger
|
|
{
|
|
/** The default constructor for the `StdForwardLogger`.
|
|
|
|
Params:
|
|
lv = The `LogLevel` for the `MultiLogger`. By default the $(D
|
|
LogLevel) is `all`.
|
|
*/
|
|
this(const LogLevel lv = LogLevel.all) @safe
|
|
{
|
|
super(lv);
|
|
this.fatalHandler = delegate() {};
|
|
}
|
|
|
|
override protected void writeLogMsg(ref LogEntry payload) @trusted
|
|
{
|
|
synchronized (sharedLog.mutex)
|
|
{
|
|
(cast() sharedLog).forwardMsg(payload);
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
auto nl1 = new StdForwardLogger(LogLevel.all);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import core.thread : Thread, msecs;
|
|
|
|
static class RaceLogger : Logger
|
|
{
|
|
int value;
|
|
this() @safe shared
|
|
{
|
|
super(LogLevel.init);
|
|
}
|
|
override void writeLogMsg(ref LogEntry payload) @safe
|
|
{
|
|
import core.thread : Thread, msecs;
|
|
if (payload.msg == "foo")
|
|
{
|
|
value = 42;
|
|
() @trusted { Thread.sleep(100.msecs); }();
|
|
assert(value == 42, "Another thread changed the value");
|
|
}
|
|
else
|
|
{
|
|
() @trusted { Thread.sleep(50.msecs); } ();
|
|
value = 13;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto oldSharedLog = sharedLog;
|
|
|
|
sharedLog = new shared RaceLogger;
|
|
scope(exit)
|
|
{
|
|
sharedLog = oldSharedLog;
|
|
}
|
|
Thread toWaitFor;
|
|
() @trusted { toWaitFor = new Thread(() { log("foo"); }).start(); }();
|
|
log("bar");
|
|
|
|
() @trusted
|
|
{
|
|
toWaitFor.join();
|
|
}();
|
|
}
|
|
|
|
/** This `LogLevel` is unqiue to every thread.
|
|
|
|
The thread local `Logger` will use this `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
|
|
{
|
|
import core.lifetime : emplace;
|
|
|
|
static align(__traits(classInstanceAlignment, StdForwardLogger))
|
|
void[__traits(classInstanceSize, StdForwardLogger)] buffer;
|
|
|
|
if (stdLoggerDefaultThreadLogger is null)
|
|
{
|
|
stdLoggerDefaultThreadLogger = emplace!StdForwardLogger(buffer, LogLevel.all);
|
|
}
|
|
return stdLoggerDefaultThreadLogger;
|
|
}
|
|
|
|
/** This function returns a thread unique `Logger`, that by default
|
|
propagates all data logged to it to the `sharedLog`.
|
|
|
|
These properties can be used to set and get this `Logger`. Every
|
|
modification to this `Logger` will only be visible in the thread the
|
|
modification has been done from.
|
|
|
|
This `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
|
|
@system unittest
|
|
{
|
|
import std.logger.filelogger : FileLogger;
|
|
import std.file : deleteme, remove;
|
|
Logger l = stdThreadLocalLog;
|
|
stdThreadLocalLog = new FileLogger(deleteme ~ "-someFile.log");
|
|
scope(exit) remove(deleteme ~ "-someFile.log");
|
|
|
|
auto tempLog = stdThreadLocalLog;
|
|
stdThreadLocalLog = l;
|
|
destroy(tempLog);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
LogLevel ll = globalLogLevel;
|
|
globalLogLevel = LogLevel.fatal;
|
|
assert(globalLogLevel == LogLevel.fatal);
|
|
globalLogLevel = ll;
|
|
}
|
|
|
|
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.all) @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;
|
|
}
|
|
}
|
|
|
|
version (StdUnittest) 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.logger.core.testFuncNames", tl1.func);
|
|
assert(tl1.prettyFunc ==
|
|
"void std.logger.core.testFuncNames(Logger logger) @safe",
|
|
tl1.prettyFunc);
|
|
assert(tl1.msg == "I'm here", tl1.msg);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto tl1 = new TestLogger(LogLevel.all);
|
|
tl1.log();
|
|
assert(tl1.line == __LINE__ - 1);
|
|
tl1.log(true);
|
|
assert(tl1.line == __LINE__ - 1);
|
|
tl1.log(false);
|
|
assert(tl1.line == __LINE__ - 3);
|
|
tl1.log(LogLevel.info);
|
|
assert(tl1.line == __LINE__ - 1);
|
|
tl1.log(LogLevel.off);
|
|
assert(tl1.line == __LINE__ - 3);
|
|
tl1.log(LogLevel.info, true);
|
|
assert(tl1.line == __LINE__ - 1);
|
|
tl1.log(LogLevel.info, false);
|
|
assert(tl1.line == __LINE__ - 3);
|
|
|
|
auto oldunspecificLogger = sharedLog;
|
|
scope(exit) {
|
|
sharedLog = atomicLoad(oldunspecificLogger);
|
|
}
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) tl1;
|
|
}();
|
|
|
|
log();
|
|
assert(tl1.line == __LINE__ - 1);
|
|
|
|
log(LogLevel.info);
|
|
assert(tl1.line == __LINE__ - 1);
|
|
|
|
log(true);
|
|
assert(tl1.line == __LINE__ - 1);
|
|
|
|
log(LogLevel.warning, true);
|
|
assert(tl1.line == __LINE__ - 1);
|
|
|
|
trace();
|
|
assert(tl1.line == __LINE__ - 1);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.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
|
|
{
|
|
import std.conv : to;
|
|
import std.exception : assertThrown, assertNotThrown;
|
|
import std.format : format;
|
|
|
|
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, l.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);
|
|
|
|
Logger oldunspecificLogger;
|
|
() @trusted {
|
|
oldunspecificLogger = cast() sharedLog;
|
|
}();
|
|
|
|
assert(oldunspecificLogger.logLevel == LogLevel.info,
|
|
to!string(oldunspecificLogger.logLevel));
|
|
|
|
assert(l.logLevel == LogLevel.all);
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) l;
|
|
}();
|
|
|
|
assert(globalLogLevel == LogLevel.all,
|
|
to!string(globalLogLevel));
|
|
|
|
scope(exit)
|
|
{
|
|
() @trusted {
|
|
sharedLog = atomicLoad(cast(shared) oldunspecificLogger);
|
|
}();
|
|
}
|
|
|
|
() @trusted {
|
|
assert((cast() 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"), l.msg);
|
|
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);
|
|
}
|
|
|
|
@system unittest // default logger
|
|
{
|
|
import std.file : deleteme, exists, remove;
|
|
import std.stdio : File;
|
|
import std.string : indexOf;
|
|
|
|
string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
|
|
FileLogger l = new FileLogger(filename);
|
|
auto oldunspecificLogger = sharedLog;
|
|
|
|
sharedLog = cast(shared) l;
|
|
|
|
scope(exit)
|
|
{
|
|
remove(filename);
|
|
assert(!exists(filename));
|
|
sharedLog = atomicLoad(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();
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.file : deleteme, remove;
|
|
import std.stdio : File;
|
|
import std.string : indexOf;
|
|
|
|
string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
|
|
auto oldunspecificLogger = sharedLog;
|
|
|
|
scope(exit)
|
|
{
|
|
remove(filename);
|
|
sharedLog = atomicLoad(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 = cast(shared) l;
|
|
|
|
() @trusted {
|
|
(cast() 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
|
|
{
|
|
import std.conv : to;
|
|
|
|
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
|
|
{
|
|
import std.conv : to;
|
|
import std.format : format;
|
|
import std.string : indexOf;
|
|
|
|
auto oldunspecificLogger = sharedLog;
|
|
|
|
auto mem = new TestLogger;
|
|
mem.fatalHandler = delegate() {};
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) mem;
|
|
}();
|
|
|
|
scope(exit)
|
|
{
|
|
sharedLog = atomicLoad(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 ll2Off = (ll2 != LogLevel.off);
|
|
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 = ll2Off && 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) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~
|
|
"cond(%b) condValue(%b)" ~
|
|
" memOrG(%b) shouldLog(%b) %s == %s" ~
|
|
" %b %b %b %b %b",
|
|
lineCall, ll2Off, gll, ll, ll2, cond,
|
|
condValue, memOrG, shouldLog, mem.msg,
|
|
valueStr, gllOff, llOff, condFalse,
|
|
ll2VSgll, ll2VSll
|
|
));
|
|
}
|
|
else
|
|
{
|
|
assert(mem.msg.indexOf(valueStr),
|
|
format(
|
|
"lineCall(%d) ll2Off(%u) gll(%u) ll(%u) ll2(%u) " ~
|
|
"cond(%b) condValue(%b)" ~
|
|
" memOrG(%b) shouldLog(%b) %s == %s" ~
|
|
" %b %b %b %b %b",
|
|
lineCall, ll2Off, gll, ll, ll2, cond,
|
|
condValue, memOrG, shouldLog, mem.msg,
|
|
valueStr, gllOff, llOff, condFalse,
|
|
ll2VSgll, ll2VSll
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// more testing
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
import std.format : format;
|
|
import std.string : indexOf;
|
|
|
|
auto oldunspecificLogger = sharedLog;
|
|
|
|
auto mem = new TestLogger;
|
|
mem.fatalHandler = delegate() {};
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) mem;
|
|
}();
|
|
|
|
scope(exit)
|
|
{
|
|
sharedLog = atomicLoad(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;
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) mem;
|
|
}();
|
|
|
|
scope(exit)
|
|
{
|
|
sharedLog = atomicLoad(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
|
|
{
|
|
import std.string : indexOf;
|
|
|
|
auto oldunspecificLogger = sharedLog;
|
|
|
|
scope(exit)
|
|
{
|
|
sharedLog = atomicLoad(oldunspecificLogger);
|
|
globalLogLevel = LogLevel.all;
|
|
}
|
|
|
|
auto tl = new TestLogger(LogLevel.info);
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) tl;
|
|
}();
|
|
|
|
trace("trace");
|
|
assert(tl.msg.indexOf("trace") == -1);
|
|
}
|
|
|
|
// Issue #5
|
|
@safe unittest
|
|
{
|
|
import std.logger.multilogger : MultiLogger;
|
|
import std.string : indexOf;
|
|
|
|
stdThreadLocalLog.logLevel = LogLevel.all;
|
|
|
|
auto oldunspecificLogger = sharedLog;
|
|
|
|
scope(exit)
|
|
{
|
|
sharedLog = atomicLoad(oldunspecificLogger);
|
|
globalLogLevel = LogLevel.all;
|
|
}
|
|
|
|
auto logger = new MultiLogger(LogLevel.error);
|
|
|
|
auto tl = new TestLogger(LogLevel.info);
|
|
logger.insertLogger("required", tl);
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) 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);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.exception : assertThrown;
|
|
auto tl = new TestLogger();
|
|
assertThrown!Throwable(tl.fatal("fatal"));
|
|
}
|
|
|
|
// log objects with non-safe toString
|
|
@system 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
|
|
@system unittest
|
|
{
|
|
import core.thread, std.concurrency;
|
|
|
|
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 = atomicLoad(oldSharedLog);
|
|
}
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) 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 = () @trusted {
|
|
return cast(FileLogger) cast() sharedLog;
|
|
}();
|
|
assert(dl !is null);
|
|
assert(dl.logLevel == LogLevel.info);
|
|
assert(globalLogLevel == LogLevel.all);
|
|
|
|
auto tl = cast(StdForwardLogger) stdThreadLocalLog;
|
|
assert(tl !is null);
|
|
stdThreadLocalLog.logLevel = LogLevel.all;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=14940
|
|
@safe unittest
|
|
{
|
|
import std.typecons : Nullable;
|
|
|
|
Nullable!int a = 1;
|
|
auto l = new TestLogger();
|
|
l.infof("log: %s", a);
|
|
assert(l.msg == "log: 1");
|
|
}
|
|
|
|
// Ensure @system toString methods work
|
|
@system unittest
|
|
{
|
|
enum SystemToStringMsg = "SystemToString";
|
|
static struct SystemToString
|
|
{
|
|
string toString() @system
|
|
{
|
|
return SystemToStringMsg;
|
|
}
|
|
}
|
|
|
|
auto tl = new TestLogger();
|
|
|
|
SystemToString sts;
|
|
tl.logf("%s", sts);
|
|
assert(tl.msg == SystemToStringMsg);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17328
|
|
@safe unittest
|
|
{
|
|
import std.format : format;
|
|
|
|
ubyte[] data = [0];
|
|
string s = format("%(%02x%)", data); // format 00
|
|
assert(s == "00");
|
|
|
|
auto tl = new TestLogger();
|
|
|
|
tl.infof("%(%02x%)", data); // infof 000
|
|
|
|
size_t i;
|
|
string fs = tl.msg;
|
|
for (; i < s.length; ++i)
|
|
{
|
|
assert(s[s.length - 1 - i] == fs[fs.length - 1 - i], fs);
|
|
}
|
|
assert(fs.length == 2);
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15954
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
auto tl = new TestLogger();
|
|
tl.log("123456789".to!wstring);
|
|
assert(tl.msg == "123456789");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=16256
|
|
@safe unittest
|
|
{
|
|
import std.conv : to;
|
|
auto tl = new TestLogger();
|
|
tl.log("123456789"d);
|
|
assert(tl.msg == "123456789");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=15517
|
|
@system unittest
|
|
{
|
|
import std.file : exists, remove, tempDir;
|
|
import std.path : buildPath;
|
|
import std.stdio : File;
|
|
import std.string : indexOf;
|
|
|
|
string fn = tempDir.buildPath("logfile.log");
|
|
if (exists(fn))
|
|
{
|
|
remove(fn);
|
|
}
|
|
|
|
auto oldShared = sharedLog;
|
|
scope(exit)
|
|
{
|
|
sharedLog = atomicLoad(oldShared);
|
|
if (exists(fn))
|
|
{
|
|
remove(fn);
|
|
}
|
|
}
|
|
|
|
auto ts = [ "Test log 1", "Test log 2", "Test log 3"];
|
|
|
|
auto fl = new FileLogger(fn);
|
|
|
|
() @trusted {
|
|
sharedLog = cast(shared) fl;
|
|
}();
|
|
|
|
assert(exists(fn));
|
|
|
|
foreach (t; ts)
|
|
{
|
|
log(t);
|
|
}
|
|
|
|
auto f = File(fn);
|
|
auto l = f.byLine();
|
|
assert(!l.empty);
|
|
size_t idx;
|
|
foreach (it; l)
|
|
{
|
|
assert(it.indexOf(ts[idx]) != -1, it);
|
|
++idx;
|
|
}
|
|
|
|
assert(exists(fn));
|
|
fl.file.close();
|
|
}
|