// 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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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);
--------------------
*/
pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
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)
{
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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);
    }

    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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)
{
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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);
    }

    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
        --------------------
        */
        pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
        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");
        --------------------
        */
        pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
        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);
        --------------------
        */
        pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
        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);
        --------------------
        */
        pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
        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);
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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");
    --------------------
    */
    pragma(inline, true) // LDC: Must inline because of __FILE__ as template parameter
    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();
}
