diff options
author | Iain Buclaw <ibuclaw@gdcproject.org> | 2022-08-25 19:04:50 +0200 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gdcproject.org> | 2022-08-28 00:16:34 +0200 |
commit | b7a586beae1027ea0c82411637920a5032d1dedf (patch) | |
tree | 4c41a84c4113e90cd0caaa7aa9925f4232dc22d5 /libphobos | |
parent | cace77f4fb8df18c01dfdf9040cc944eedef1147 (diff) | |
download | gcc-b7a586beae1027ea0c82411637920a5032d1dedf.zip gcc-b7a586beae1027ea0c82411637920a5032d1dedf.tar.gz gcc-b7a586beae1027ea0c82411637920a5032d1dedf.tar.bz2 |
d: Merge upstream dmd 817610b16d, phobos b578dfad9
D front-end changes:
- Import latest bug fixes to mainline.
Phobos changes:
- Import latest bug fixes to mainline.
- std.logger module has been moved out of experimental.
- Removed std.experimental.typecons module.
gcc/d/ChangeLog:
* dmd/MERGE: Merge upstream dmd 817610b16d.
* d-ctfloat.cc (CTFloat::parse): Update for new front-end interface.
* d-lang.cc (d_parse_file): Likewise.
* expr.cc (ExprVisitor::visit (AssignExp *)): Remove handling of array
assignments to non-trivial static and dynamic arrays.
* runtime.def (ARRAYASSIGN): Remove.
(ARRAYASSIGN_L): Remove.
(ARRAYASSIGN_R): Remove.
libphobos/ChangeLog:
* libdruntime/MERGE: Merge upstream druntime 817610b16d.
* libdruntime/Makefile.am (DRUNTIME_DSOURCES): Add
core/internal/array/arrayassign.d.
* libdruntime/Makefile.in: Regenerate.
* src/MERGE: Merge upstream phobos b578dfad9.
* src/Makefile.am (PHOBOS_DSOURCES): Remove
std/experimental/typecons.d. Add std/logger package.
* src/Makefile.in: Regenerate.
Diffstat (limited to 'libphobos')
39 files changed, 4398 insertions, 5135 deletions
diff --git a/libphobos/libdruntime/MERGE b/libphobos/libdruntime/MERGE index c358b69..85fc49d 100644 --- a/libphobos/libdruntime/MERGE +++ b/libphobos/libdruntime/MERGE @@ -1,4 +1,4 @@ -d7772a236983ec37b92d21b28bad3cd2de57b945 +817610b16d0f0f469b9fbb28c000956fb910c43f The first line of this file holds the git revision number of the last merge done from the dlang/dmd repository. diff --git a/libphobos/libdruntime/Makefile.am b/libphobos/libdruntime/Makefile.am index 2e1e91d..d828749 100644 --- a/libphobos/libdruntime/Makefile.am +++ b/libphobos/libdruntime/Makefile.am @@ -171,17 +171,18 @@ DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \ core/builtins.d core/checkedint.d core/cpuid.d core/demangle.d \ core/exception.d core/gc/config.d core/gc/gcinterface.d \ core/gc/registry.d core/int128.d core/internal/abort.d \ - core/internal/array/appending.d core/internal/array/capacity.d \ - core/internal/array/casting.d core/internal/array/comparison.d \ - core/internal/array/concatenation.d core/internal/array/construction.d \ - core/internal/array/duplication.d core/internal/array/equality.d \ - core/internal/array/operations.d core/internal/array/utils.d \ - core/internal/atomic.d core/internal/attributes.d \ - core/internal/container/array.d core/internal/container/common.d \ - core/internal/container/hashtab.d core/internal/container/treap.d \ - core/internal/convert.d core/internal/dassert.d \ - core/internal/destruction.d core/internal/entrypoint.d \ - core/internal/gc/bits.d core/internal/gc/impl/conservative/gc.d \ + core/internal/array/appending.d core/internal/array/arrayassign.d \ + core/internal/array/capacity.d core/internal/array/casting.d \ + core/internal/array/comparison.d core/internal/array/concatenation.d \ + core/internal/array/construction.d core/internal/array/duplication.d \ + core/internal/array/equality.d core/internal/array/operations.d \ + core/internal/array/utils.d core/internal/atomic.d \ + core/internal/attributes.d core/internal/container/array.d \ + core/internal/container/common.d core/internal/container/hashtab.d \ + core/internal/container/treap.d core/internal/convert.d \ + core/internal/dassert.d core/internal/destruction.d \ + core/internal/entrypoint.d core/internal/gc/bits.d \ + core/internal/gc/impl/conservative/gc.d \ core/internal/gc/impl/manual/gc.d core/internal/gc/impl/proto/gc.d \ core/internal/gc/os.d core/internal/gc/pooltable.d \ core/internal/gc/proxy.d core/internal/hash.d core/internal/lifetime.d \ diff --git a/libphobos/libdruntime/Makefile.in b/libphobos/libdruntime/Makefile.in index de6656c..57660ee 100644 --- a/libphobos/libdruntime/Makefile.in +++ b/libphobos/libdruntime/Makefile.in @@ -192,6 +192,7 @@ am__objects_1 = core/atomic.lo core/attribute.lo core/bitop.lo \ core/demangle.lo core/exception.lo core/gc/config.lo \ core/gc/gcinterface.lo core/gc/registry.lo core/int128.lo \ core/internal/abort.lo core/internal/array/appending.lo \ + core/internal/array/arrayassign.lo \ core/internal/array/capacity.lo core/internal/array/casting.lo \ core/internal/array/comparison.lo \ core/internal/array/concatenation.lo \ @@ -839,17 +840,18 @@ DRUNTIME_DSOURCES = core/atomic.d core/attribute.d core/bitop.d \ core/builtins.d core/checkedint.d core/cpuid.d core/demangle.d \ core/exception.d core/gc/config.d core/gc/gcinterface.d \ core/gc/registry.d core/int128.d core/internal/abort.d \ - core/internal/array/appending.d core/internal/array/capacity.d \ - core/internal/array/casting.d core/internal/array/comparison.d \ - core/internal/array/concatenation.d core/internal/array/construction.d \ - core/internal/array/duplication.d core/internal/array/equality.d \ - core/internal/array/operations.d core/internal/array/utils.d \ - core/internal/atomic.d core/internal/attributes.d \ - core/internal/container/array.d core/internal/container/common.d \ - core/internal/container/hashtab.d core/internal/container/treap.d \ - core/internal/convert.d core/internal/dassert.d \ - core/internal/destruction.d core/internal/entrypoint.d \ - core/internal/gc/bits.d core/internal/gc/impl/conservative/gc.d \ + core/internal/array/appending.d core/internal/array/arrayassign.d \ + core/internal/array/capacity.d core/internal/array/casting.d \ + core/internal/array/comparison.d core/internal/array/concatenation.d \ + core/internal/array/construction.d core/internal/array/duplication.d \ + core/internal/array/equality.d core/internal/array/operations.d \ + core/internal/array/utils.d core/internal/atomic.d \ + core/internal/attributes.d core/internal/container/array.d \ + core/internal/container/common.d core/internal/container/hashtab.d \ + core/internal/container/treap.d core/internal/convert.d \ + core/internal/dassert.d core/internal/destruction.d \ + core/internal/entrypoint.d core/internal/gc/bits.d \ + core/internal/gc/impl/conservative/gc.d \ core/internal/gc/impl/manual/gc.d core/internal/gc/impl/proto/gc.d \ core/internal/gc/os.d core/internal/gc/pooltable.d \ core/internal/gc/proxy.d core/internal/hash.d core/internal/lifetime.d \ @@ -1201,6 +1203,8 @@ core/internal/array/$(am__dirstamp): @$(MKDIR_P) core/internal/array @: > core/internal/array/$(am__dirstamp) core/internal/array/appending.lo: core/internal/array/$(am__dirstamp) +core/internal/array/arrayassign.lo: \ + core/internal/array/$(am__dirstamp) core/internal/array/capacity.lo: core/internal/array/$(am__dirstamp) core/internal/array/casting.lo: core/internal/array/$(am__dirstamp) core/internal/array/comparison.lo: \ diff --git a/libphobos/libdruntime/core/demangle.d b/libphobos/libdruntime/core/demangle.d index cb8d433..ca87f90 100644 --- a/libphobos/libdruntime/core/demangle.d +++ b/libphobos/libdruntime/core/demangle.d @@ -2328,7 +2328,7 @@ char[] mangle(T)(return scope const(char)[] fqn, return scope char[] dst = null) @property bool empty() const { return !s.length; } - @property const(char)[] front() const return + @property const(char)[] front() const return scope { immutable i = indexOfDot(); return i == -1 ? s[0 .. $] : s[0 .. i]; diff --git a/libphobos/libdruntime/core/exception.d b/libphobos/libdruntime/core/exception.d index 81aa43b..a05a24c 100644 --- a/libphobos/libdruntime/core/exception.d +++ b/libphobos/libdruntime/core/exception.d @@ -14,7 +14,7 @@ void __switch_errorT()(string file = __FILE__, size_t line = __LINE__) @trusted { // Consider making this a compile time check. version (D_Exceptions) - throw staticError!SwitchError(file, line, null); + throw staticError!SwitchError("No appropriate switch clause found", file, line, null); else assert(0, "No appropriate switch clause found"); } @@ -446,16 +446,16 @@ class ForkError : Error */ class SwitchError : Error { - @safe pure nothrow @nogc this( string file = __FILE__, size_t line = __LINE__, Throwable next = null ) + @safe pure nothrow @nogc this( string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null ) { - super( "No appropriate switch clause found", file, line, next ); + super( msg, file, line, next ); } } unittest { { - auto se = new SwitchError(); + auto se = new SwitchError("No appropriate switch clause found"); assert(se.file == __FILE__); assert(se.line == __LINE__ - 2); assert(se.next is null); @@ -463,7 +463,7 @@ unittest } { - auto se = new SwitchError("hello", 42, new Exception("It's an Exception!")); + auto se = new SwitchError("No appropriate switch clause found", "hello", 42, new Exception("It's an Exception!")); assert(se.file == "hello"); assert(se.line == 42); assert(se.next !is null); diff --git a/libphobos/libdruntime/core/internal/array/arrayassign.d b/libphobos/libdruntime/core/internal/array/arrayassign.d new file mode 100644 index 0000000..6132e68 --- /dev/null +++ b/libphobos/libdruntime/core/internal/array/arrayassign.d @@ -0,0 +1,304 @@ +module core.internal.array.arrayassign; + +// Force `enforceRawArraysConformable` to remain `pure` `@nogc` +private void enforceRawArraysConformable(const char[] action, const size_t elementSize, + const void[] a1, const void[] a2, const bool allowOverlap) @trusted @nogc pure nothrow +{ + import core.internal.util.array : enforceRawArraysConformable; + + alias Type = void function(const char[] action, const size_t elementSize, + const void[] a1, const void[] a2, in bool allowOverlap = false) @nogc pure nothrow; + (cast(Type)&enforceRawArraysConformable)(action, elementSize, a1, a2, allowOverlap); +} + +private template CopyElem(string CopyAction) +{ + const char[] CopyElem = "{\n" ~ q{ + memcpy(&tmp, cast(void*) &dst, elemSize); + } ~ CopyAction ~ q{ + auto elem = cast(Unqual!T*) &tmp; + destroy(*elem); + } ~ "}\n"; +} + +private template CopyArray(bool CanOverlap, string CopyAction) +{ + const char[] CopyArray = CanOverlap ? q{ + if (vFrom.ptr < vTo.ptr && vTo.ptr < vFrom.ptr + elemSize * vFrom.length) + foreach_reverse (i, ref dst; to) + } ~ CopyElem!(CopyAction) ~ q{ + else + foreach (i, ref dst; to) + } ~ CopyElem!(CopyAction) + : q{ + foreach (i, ref dst; to) + } ~ CopyElem!(CopyAction); +} + +private template ArrayAssign(string CopyLogic, string AllowOverLap) +{ + const char[] ArrayAssign = q{ + import core.internal.traits : hasElaborateCopyConstructor, Unqual; + import core.lifetime : copyEmplace; + import core.stdc.string : memcpy; + + void[] vFrom = (cast(void*) from.ptr)[0 .. from.length]; + void[] vTo = (cast(void*) to.ptr)[0 .. to.length]; + enum elemSize = T.sizeof; + + enforceRawArraysConformable("copy", elemSize, vFrom, vTo, } ~ AllowOverLap ~ q{); + + void[elemSize] tmp = void; + + } ~ CopyLogic ~ q{ + + return to; + }; +} + +/** + * Does array assignment (not construction) from another array of the same + * element type. Handles overlapping copies. Assumes the right hand side is an + * lvalue, + * + * Used for static array assignment with non-POD element types: + * --- + * struct S + * { + * ~this() {} // destructor, so not Plain Old Data + * } + * + * void main() + * { + * S[3] arr; + * S[3] lvalue; + * + * arr = lvalue; + * // Generates: + * // _d_arrayassign_l(arr[], lvalue[]), arr; + * } + * --- + * + * Params: + * to = destination array + * from = source array + * Returns: + * `to` + */ +Tarr _d_arrayassign_l(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + mixin(ArrayAssign!(q{ + static if (hasElaborateCopyConstructor!T) + } ~ CopyArray!(true, "copyEmplace(from[i], dst);") ~ q{ + else + } ~ CopyArray!(true, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), + "true")); +} + +@safe unittest +{ + int counter; + struct S + { + int val; + this(int val) { this.val = val; } + this(const scope ref S rhs) + { + val = rhs.val; + counter++; + } + } + + S[4] arr1; + S[4] arr2 = [S(0), S(1), S(2), S(3)]; + _d_arrayassign_l(arr1[], arr2[]); + + assert(counter == 4); + assert(arr1 == arr2); +} + +// copy constructor +@safe unittest +{ + int counter; + struct S + { + int val; + this(int val) { this.val = val; } + this(const scope ref S rhs) + { + val = rhs.val; + counter++; + } + } + + S[4] arr1; + S[4] arr2 = [S(0), S(1), S(2), S(3)]; + _d_arrayassign_l(arr1[], arr2[]); + + assert(counter == 4); + assert(arr1 == arr2); +} + +@safe nothrow unittest +{ + // Test that throwing works + int counter; + bool didThrow; + + struct Throw + { + int val; + this(this) + { + counter++; + if (counter == 2) + throw new Exception(""); + } + } + try + { + Throw[4] a; + Throw[4] b = [Throw(1), Throw(2), Throw(3), Throw(4)]; + _d_arrayassign_l(a[], b[]); + } + catch (Exception) + { + didThrow = true; + } + assert(didThrow); + assert(counter == 2); + + + // Test that `nothrow` works + didThrow = false; + counter = 0; + struct NoThrow + { + int val; + this(this) + { + counter++; + } + } + try + { + NoThrow[4] a; + NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; + _d_arrayassign_l(a[], b[]); + } + catch (Exception) + { + didThrow = false; + } + assert(!didThrow); + assert(counter == 4); +} + +/** + * Does array assignment (not construction) from another array of the same + * element type. Does not support overlapping copies. Assumes the right hand + * side is an rvalue, + * + * Used for static array assignment with non-POD element types: + * --- + * struct S + * { + * ~this() {} // destructor, so not Plain Old Data + * } + * + * void main() + * { + * S[3] arr; + * S[3] getRvalue() {return lvalue;} + * + * arr = getRvalue(); + * // Generates: + * // (__appendtmp = getRvalue), _d_arrayassign_l(arr[], __appendtmp), arr; + * } + * --- + * + * Params: + * to = destination array + * from = source array + * Returns: + * `to` + */ +Tarr _d_arrayassign_r(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + mixin(ArrayAssign!( + CopyArray!(false, "memcpy(cast(void*) &dst, cast(void*) &from[i], elemSize);"), + "false")); +} + +@safe unittest +{ + int counter; + struct S + { + int val; + this(int val) { this.val = val; } + this(const scope ref S rhs) + { + val = rhs.val; + counter++; + } + } + + S[4] arr1; + S[4] arr2 = [S(0), S(1), S(2), S(3)]; + _d_arrayassign_r(arr1[], arr2[]); + + assert(counter == 0); + assert(arr1 == arr2); +} + +// copy constructor +@safe unittest +{ + int counter; + struct S + { + int val; + this(int val) { this.val = val; } + this(const scope ref S rhs) + { + val = rhs.val; + counter++; + } + } + + S[4] arr1; + S[4] arr2 = [S(0), S(1), S(2), S(3)]; + _d_arrayassign_r(arr1[], arr2[]); + + assert(counter == 0); + assert(arr1 == arr2); +} + +@safe nothrow unittest +{ + // Test that `nothrow` works + bool didThrow = false; + int counter = 0; + struct NoThrow + { + int val; + this(this) + { + counter++; + } + } + try + { + NoThrow[4] a; + NoThrow[4] b = [NoThrow(1), NoThrow(2), NoThrow(3), NoThrow(4)]; + _d_arrayassign_r(a[], b[]); + } + catch (Exception) + { + didThrow = false; + } + assert(!didThrow); + assert(counter == 0); +} diff --git a/libphobos/libdruntime/core/internal/array/equality.d b/libphobos/libdruntime/core/internal/array/equality.d index d3fdd65..c110d64 100644 --- a/libphobos/libdruntime/core/internal/array/equality.d +++ b/libphobos/libdruntime/core/internal/array/equality.d @@ -236,6 +236,33 @@ unittest static assert(!useMemcmp!(int[], int[])); } +// https://issues.dlang.org/show_bug.cgi?id=21094 +unittest +{ + static class C + { + int a; + } + static struct S + { + bool isValid; + C fib; + + inout(C) get() pure @safe @nogc nothrow inout + { + return isValid ? fib : C.init; + } + T opCast(T : C)() const { return null; } + + alias get this; + } + + auto foo(S[] lhs, S[] rhs) + { + return lhs == rhs; + } +} + // Returns a reference to an array element, eliding bounds check and // casting void to ubyte. pragma(inline, true) diff --git a/libphobos/libdruntime/core/sys/posix/sys/socket.d b/libphobos/libdruntime/core/sys/posix/sys/socket.d index 3a7b753..fc5dc5d 100644 --- a/libphobos/libdruntime/core/sys/posix/sys/socket.d +++ b/libphobos/libdruntime/core/sys/posix/sys/socket.d @@ -188,10 +188,40 @@ version (linux) extern (D) inout(ubyte)* CMSG_DATA( return scope inout(cmsghdr)* cmsg ) pure nothrow @nogc { return cast(ubyte*)( cmsg + 1 ); } - private inout(cmsghdr)* __cmsg_nxthdr(inout(msghdr)*, inout(cmsghdr)*) pure nothrow @nogc; - extern (D) inout(cmsghdr)* CMSG_NXTHDR(inout(msghdr)* msg, inout(cmsghdr)* cmsg) pure nothrow @nogc + version (CRuntime_Musl) { - return __cmsg_nxthdr(msg, cmsg); + extern (D) + { + private size_t __CMSG_LEN(inout(cmsghdr)* cmsg) pure nothrow @nogc + { + return (cmsg.cmsg_len + size_t.sizeof -1) & cast(size_t)(~(size_t.sizeof - 1)); + } + + private inout(cmsghdr)* __CMSG_NEXT(inout(cmsghdr)* cmsg) pure nothrow @nogc + { + return cmsg + __CMSG_LEN(cmsg); + } + + private inout(msghdr)* __MHDR_END(inout(msghdr)* mhdr) pure nothrow @nogc + { + return cast(inout(msghdr)*)(mhdr.msg_control + mhdr.msg_controllen); + } + + inout(cmsghdr)* CMSG_NXTHDR(inout(msghdr)* msg, inout(cmsghdr)* cmsg) pure nothrow @nogc + { + return cmsg.cmsg_len < cmsghdr.sizeof || + __CMSG_LEN(cmsg) + cmsghdr.sizeof >= __MHDR_END(msg) - cast(inout(msghdr)*)(cmsg) + ? cast(inout(cmsghdr)*) null : cast(inout(cmsghdr)*) __CMSG_NEXT(cmsg); + } + } + } + else + { + private inout(cmsghdr)* __cmsg_nxthdr(inout(msghdr)*, inout(cmsghdr)*) pure nothrow @nogc; + extern (D) inout(cmsghdr)* CMSG_NXTHDR(inout(msghdr)* msg, inout(cmsghdr)* cmsg) pure nothrow @nogc + { + return __cmsg_nxthdr(msg, cmsg); + } } extern (D) inout(cmsghdr)* CMSG_FIRSTHDR( inout(msghdr)* mhdr ) pure nothrow @nogc diff --git a/libphobos/libdruntime/object.d b/libphobos/libdruntime/object.d index 8ef65489..d842499 100644 --- a/libphobos/libdruntime/object.d +++ b/libphobos/libdruntime/object.d @@ -6,6 +6,7 @@ * $(TR $(TD Arrays) $(TD * $(MYREF assumeSafeAppend) * $(MYREF capacity) + * $(A #.dup.2, $(TT dup)) * $(MYREF idup) * $(MYREF reserve) * )) @@ -14,6 +15,7 @@ * $(MYREF byKeyValue) * $(MYREF byValue) * $(MYREF clear) + * $(MYREF dup) * $(MYREF get) * $(MYREF keys) * $(MYREF rehash) @@ -23,15 +25,15 @@ * )) * $(TR $(TD General) $(TD * $(MYREF destroy) - * $(MYREF dup) * $(MYREF hashOf) - * $(MYREF opEquals) + * $(MYREF imported) + * $(MYREF noreturn) * )) - * $(TR $(TD Types) $(TD + * $(TR $(TD Classes) $(TD * $(MYREF Error) * $(MYREF Exception) - * $(MYREF noreturn) * $(MYREF Object) + * $(MYREF opEquals) * $(MYREF Throwable) * )) * $(TR $(TD Type info) $(TD @@ -61,7 +63,11 @@ alias size_t = typeof(int.sizeof); alias ptrdiff_t = typeof(cast(void*)0 - cast(void*)0); alias sizediff_t = ptrdiff_t; // For backwards compatibility only. -alias noreturn = typeof(*null); /// bottom type +/** + * Bottom type. + * See $(DDSUBLINK spec/type, noreturn). + */ +alias noreturn = typeof(*null); alias hash_t = size_t; // For backwards compatibility only. alias equals_t = bool; // For backwards compatibility only. @@ -266,7 +272,9 @@ class Object the typeinfo name string compare. This is because of dmd's dll implementation. However, it can infer to @safe if your class' opEquals is. +/ -bool opEquals(LHS, RHS)(LHS lhs, RHS rhs) if (is(LHS : const Object) && is(RHS : const Object)) +bool opEquals(LHS, RHS)(LHS lhs, RHS rhs) +if ((is(LHS : const Object) || is(LHS : const shared Object)) && + (is(RHS : const Object) || is(RHS : const shared Object))) { static if (__traits(compiles, lhs.opEquals(rhs)) && __traits(compiles, rhs.opEquals(lhs))) { @@ -505,6 +513,16 @@ unittest assert(obj1 != obj2); } +// https://issues.dlang.org/show_bug.cgi?id=23291 +@system unittest +{ + static shared class C { bool opEquals(const(shared(C)) rhs) const shared { return true;}} + const(C) c = new C(); + const(C)[] a = [c]; + const(C)[] b = [c]; + assert(a[0] == b[0]); +} + private extern(C) void _d_setSameMutex(shared Object ownee, shared Object owner) nothrow; void setSameMutex(shared Object ownee, shared Object owner) @@ -3473,13 +3491,18 @@ ref V require(K, V)(ref V[K] aa, K key, lazy V value = V.init) private enum bool isSafeCopyable(T) = is(typeof(() @safe { union U { T x; } T *x; auto u = U(*x); })); /*********************************** - * Looks up key; if it exists applies the update callable else evaluates the - * create callable and adds it to the associative array + * Calls `create` if `key` doesn't exist in the associative array, + * otherwise calls `update`. + * `create` returns a corresponding value for `key`. + * `update` accepts a key parameter. If it returns a value, the value is + * set for `key`. * Params: * aa = The associative array. * key = The key. - * create = The callable to apply on create. - * update = The callable to apply on update. + * create = The callable to create a value for `key`. + * Must return V. + * update = The callable to call if `key` exists. + * Takes a K argument, returns a V or void. */ void update(K, V, C, U)(ref V[K] aa, K key, scope C create, scope U update) if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof(update(aa[K.init])) == void))) @@ -3509,23 +3532,39 @@ if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof } /// -@system unittest +@safe unittest { - auto aa = ["k1": 1]; + int[string] aa; - aa.update("k1", { - return -1; // create (won't be executed) - }, (ref int v) { - v += 1; // update - }); - assert(aa["k1"] == 2); - - aa.update("k2", { - return 0; // create - }, (ref int v) { - v = -1; // update (won't be executed) - }); - assert(aa["k2"] == 0); + // create + aa.update("key", + () => 1, + (int) {} // not executed + ); + assert(aa["key"] == 1); + + // update value by ref + aa.update("key", + () => 0, // not executed + (ref int v) { + v += 1; + }); + assert(aa["key"] == 2); + + // update from return value + aa.update("key", + () => 0, // not executed + (int v) => v * 2 + ); + assert(aa["key"] == 4); + + // 'update' without changing value + aa.update("key", + () => 0, // not executed + (int) { + // do something else + }); + assert(aa["key"] == 4); } @safe unittest @@ -4576,6 +4615,8 @@ public import core.internal.array.casting: __ArrayCast; public import core.internal.array.concatenation : _d_arraycatnTXImpl; public import core.internal.array.construction : _d_arrayctor; public import core.internal.array.construction : _d_arraysetctor; +public import core.internal.array.arrayassign : _d_arrayassign_l; +public import core.internal.array.arrayassign : _d_arrayassign_r; public import core.internal.array.capacity: _d_arraysetlengthTImpl; public import core.internal.dassert: _d_assert_fail; diff --git a/libphobos/libdruntime/rt/arrayassign.d b/libphobos/libdruntime/rt/arrayassign.d index c9db2fc..c9e2b15 100644 --- a/libphobos/libdruntime/rt/arrayassign.d +++ b/libphobos/libdruntime/rt/arrayassign.d @@ -19,171 +19,6 @@ private debug(PRINTF) import core.stdc.stdio; } -/* - * Superseded array assignment hook. Does not take into account destructors: - * https://issues.dlang.org/show_bug.cgi?id=13661 - * Kept for backward binary compatibility. This function can be removed in the future. - */ -extern (C) void[] _d_arrayassign(TypeInfo ti, void[] from, void[] to) -{ - debug(PRINTF) printf("_d_arrayassign(from = %p,%d, to = %p,%d) size = %d\n", from.ptr, from.length, to.ptr, to.length, ti.tsize); - - immutable elementSize = ti.tsize; - - // Need a temporary buffer tmp[] big enough to hold one element - void[16] buf = void; - void* ptmp = (elementSize > buf.sizeof) ? malloc(elementSize) : buf.ptr; - scope (exit) - { - if (ptmp != buf.ptr) - free(ptmp); - } - return _d_arrayassign_l(ti, from, to, ptmp); -} - -/** -Does array assignment (not construction) from another array of the same -element type. - -Handles overlapping copies. - -The `_d_arrayassign_l` variant assumes the right hand side is an lvalue, -while `_d_arrayassign_r` assumes it's an rvalue, which means it doesn't have to call copy constructors. - -Used for static array assignment with non-POD element types: ---- -struct S -{ - ~this() {} // destructor, so not Plain Old Data -} - -void main() -{ - S[3] arr; - S[3] lvalue; - - arr = lvalue; - // Generates: - // S _tmp; - // _d_arrayassign_l(typeid(S), (cast(void*) lvalue.ptr)[0..lvalue.length], (cast(void*) arr.ptr)[0..arr.length], &_tmp); - - S[3] getRvalue() {return lvalue;} - arr = getRvalue(); - // Similar, but `_d_arrayassign_r` -} ---- - -Params: - ti = `TypeInfo` of the array element type. - dst = target memory. Its `.length` is equal to the element count, not byte length. - src = source memory. Its `.length` is equal to the element count, not byte length. - ptmp = Temporary memory for element swapping, must have capacity of `ti.tsize` bytes. -Returns: `dst` -*/ -extern (C) void[] _d_arrayassign_l(TypeInfo ti, void[] src, void[] dst, void* ptmp) -{ - debug(PRINTF) printf("_d_arrayassign_l(src = %p,%d, dst = %p,%d) size = %d\n", src.ptr, src.length, dst.ptr, dst.length, ti.tsize); - - immutable elementSize = ti.tsize; - - enforceRawArraysConformable("copy", elementSize, src, dst, true); - - if (src.ptr < dst.ptr && dst.ptr < src.ptr + elementSize * src.length) - { - // If dst is in the middle of src memory, use reverse order. - for (auto i = dst.length; i--; ) - { - void* pdst = dst.ptr + i * elementSize; - void* psrc = src.ptr + i * elementSize; - memcpy(ptmp, pdst, elementSize); - memcpy(pdst, psrc, elementSize); - ti.postblit(pdst); - ti.destroy(ptmp); - } - } - else - { - // Otherwise, use normal order. - foreach (i; 0 .. dst.length) - { - void* pdst = dst.ptr + i * elementSize; - void* psrc = src.ptr + i * elementSize; - memcpy(ptmp, pdst, elementSize); - memcpy(pdst, psrc, elementSize); - ti.postblit(pdst); - ti.destroy(ptmp); - } - } - return dst; -} - -unittest // Bugzilla 14024 -{ - string op; - - struct S - { - char x = 'x'; - this(this) { op ~= x-0x20; } // upper case - ~this() { op ~= x; } // lower case - } - - S[4] mem; - ref S[2] slice(int a, int b) { return mem[a .. b][0 .. 2]; } - - op = null; - mem[0].x = 'a'; - mem[1].x = 'b'; - mem[2].x = 'x'; - mem[3].x = 'y'; - slice(0, 2) = slice(2, 4); // [ab] = [xy] - assert(op == "XaYb", op); - - op = null; - mem[0].x = 'x'; - mem[1].x = 'y'; - mem[2].x = 'a'; - mem[3].x = 'b'; - slice(2, 4) = slice(0, 2); // [ab] = [xy] - assert(op == "XaYb", op); - - op = null; - mem[0].x = 'a'; - mem[1].x = 'b'; - mem[2].x = 'c'; - slice(0, 2) = slice(1, 3); // [ab] = [bc] - assert(op == "BaCb", op); - - op = null; - mem[0].x = 'x'; - mem[1].x = 'y'; - mem[2].x = 'z'; - slice(1, 3) = slice(0, 2); // [yz] = [xy] - assert(op == "YzXy", op); -} - -/// ditto -extern (C) void[] _d_arrayassign_r(TypeInfo ti, void[] src, void[] dst, void* ptmp) -{ - debug(PRINTF) printf("_d_arrayassign_r(src = %p,%d, dst = %p,%d) size = %d\n", src.ptr, src.length, dst.ptr, dst.length, ti.tsize); - - immutable elementSize = ti.tsize; - - enforceRawArraysConformable("copy", elementSize, src, dst, false); - - // Always use normal order, because we can assume that - // the rvalue src has no overlapping with dst. - foreach (i; 0 .. dst.length) - { - void* pdst = dst.ptr + i * elementSize; - void* psrc = src.ptr + i * elementSize; - memcpy(ptmp, pdst, elementSize); - memcpy(pdst, psrc, elementSize); - ti.destroy(ptmp); - } - return dst; -} - /** Set all elements of an array to a single value. diff --git a/libphobos/src/MERGE b/libphobos/src/MERGE index 1f0cfbf..c8a3771 100644 --- a/libphobos/src/MERGE +++ b/libphobos/src/MERGE @@ -1,4 +1,4 @@ -5748ca43fd5c3e31ce7a8511f542b67e5d5a3dc6 +b578dfad94770574d7e522557a77276c35943daa The first line of this file holds the git revision number of the last merge done from the dlang/phobos repository. diff --git a/libphobos/src/Makefile.am b/libphobos/src/Makefile.am index da7a200..2413024 100644 --- a/libphobos/src/Makefile.am +++ b/libphobos/src/Makefile.am @@ -127,11 +127,10 @@ PHOBOS_DSOURCES = etc/c/curl.d etc/c/zlib.d std/algorithm/comparison.d \ std/experimental/logger/core.d std/experimental/logger/filelogger.d \ std/experimental/logger/multilogger.d \ std/experimental/logger/nulllogger.d std/experimental/logger/package.d \ - std/experimental/typecons.d std/file.d std/format/internal/floats.d \ - std/format/internal/read.d std/format/internal/write.d \ - std/format/package.d std/format/read.d std/format/spec.d \ - std/format/write.d std/functional.d std/getopt.d std/int128.d \ - std/internal/attributes.d std/internal/cstring.d \ + std/file.d std/format/internal/floats.d std/format/internal/read.d \ + std/format/internal/write.d std/format/package.d std/format/read.d \ + std/format/spec.d std/format/write.d std/functional.d std/getopt.d \ + std/int128.d std/internal/attributes.d std/internal/cstring.d \ std/internal/math/biguintcore.d std/internal/math/biguintnoasm.d \ std/internal/math/errorfunction.d std/internal/math/gammafunction.d \ std/internal/memory.d std/internal/scopebuffer.d \ @@ -139,7 +138,9 @@ PHOBOS_DSOURCES = etc/c/curl.d etc/c/zlib.d std/algorithm/comparison.d \ std/internal/test/uda.d std/internal/unicode_comp.d \ std/internal/unicode_decomp.d std/internal/unicode_grapheme.d \ std/internal/unicode_norm.d std/internal/unicode_tables.d \ - std/internal/windows/advapi32.d std/json.d std/math/algebraic.d \ + std/internal/windows/advapi32.d std/json.d std/logger/core.d \ + std/logger/filelogger.d std/logger/multilogger.d \ + std/logger/nulllogger.d std/logger/package.d std/math/algebraic.d \ std/math/constants.d std/math/exponential.d std/math/hardware.d \ std/math/operations.d std/math/package.d std/math/remainder.d \ std/math/rounding.d std/math/traits.d std/math/trigonometry.d \ diff --git a/libphobos/src/Makefile.in b/libphobos/src/Makefile.in index 6f58fee..562a428 100644 --- a/libphobos/src/Makefile.in +++ b/libphobos/src/Makefile.in @@ -218,7 +218,6 @@ am__dirstamp = $(am__leading_dot)dirstamp @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/multilogger.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/nulllogger.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/package.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/typecons.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/file.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/floats.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/read.lo \ @@ -246,7 +245,11 @@ am__dirstamp = $(am__leading_dot)dirstamp @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_norm.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_tables.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/windows/advapi32.lo \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/json.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/json.lo std/logger/core.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/logger/filelogger.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/logger/multilogger.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/logger/nulllogger.lo \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/logger/package.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/algebraic.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/constants.lo \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/exponential.lo \ @@ -589,11 +592,10 @@ libgphobos_la_LINK = $(LIBTOOL) --tag=D $(libgphobos_la_LIBTOOLFLAGS) \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/core.d std/experimental/logger/filelogger.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/multilogger.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/logger/nulllogger.d std/experimental/logger/package.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/experimental/typecons.d std/file.d std/format/internal/floats.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/read.d std/format/internal/write.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/package.d std/format/read.d std/format/spec.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/write.d std/functional.d std/getopt.d std/int128.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/attributes.d std/internal/cstring.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/file.d std/format/internal/floats.d std/format/internal/read.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/internal/write.d std/format/package.d std/format/read.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/format/spec.d std/format/write.d std/functional.d std/getopt.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/int128.d std/internal/attributes.d std/internal/cstring.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/biguintcore.d std/internal/math/biguintnoasm.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/math/errorfunction.d std/internal/math/gammafunction.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/memory.d std/internal/scopebuffer.d \ @@ -601,7 +603,9 @@ libgphobos_la_LINK = $(LIBTOOL) --tag=D $(libgphobos_la_LIBTOOLFLAGS) \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/test/uda.d std/internal/unicode_comp.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_decomp.d std/internal/unicode_grapheme.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/unicode_norm.d std/internal/unicode_tables.d \ -@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/windows/advapi32.d std/json.d std/math/algebraic.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/internal/windows/advapi32.d std/json.d std/logger/core.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/logger/filelogger.d std/logger/multilogger.d \ +@ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/logger/nulllogger.d std/logger/package.d std/math/algebraic.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/constants.d std/math/exponential.d std/math/hardware.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/operations.d std/math/package.d std/math/remainder.d \ @ENABLE_LIBDRUNTIME_ONLY_FALSE@ std/math/rounding.d std/math/traits.d std/math/trigonometry.d \ @@ -830,7 +834,6 @@ std/experimental/logger/nulllogger.lo: \ std/experimental/logger/$(am__dirstamp) std/experimental/logger/package.lo: \ std/experimental/logger/$(am__dirstamp) -std/experimental/typecons.lo: std/experimental/$(am__dirstamp) std/file.lo: std/$(am__dirstamp) std/format/internal/$(am__dirstamp): @$(MKDIR_P) std/format/internal @@ -879,6 +882,14 @@ std/internal/windows/$(am__dirstamp): std/internal/windows/advapi32.lo: \ std/internal/windows/$(am__dirstamp) std/json.lo: std/$(am__dirstamp) +std/logger/$(am__dirstamp): + @$(MKDIR_P) std/logger + @: > std/logger/$(am__dirstamp) +std/logger/core.lo: std/logger/$(am__dirstamp) +std/logger/filelogger.lo: std/logger/$(am__dirstamp) +std/logger/multilogger.lo: std/logger/$(am__dirstamp) +std/logger/nulllogger.lo: std/logger/$(am__dirstamp) +std/logger/package.lo: std/logger/$(am__dirstamp) std/math/$(am__dirstamp): @$(MKDIR_P) std/math @: > std/math/$(am__dirstamp) @@ -994,6 +1005,8 @@ mostlyclean-compile: -rm -f std/internal/test/*.lo -rm -f std/internal/windows/*.$(OBJEXT) -rm -f std/internal/windows/*.lo + -rm -f std/logger/*.$(OBJEXT) + -rm -f std/logger/*.lo -rm -f std/math/*.$(OBJEXT) -rm -f std/math/*.lo -rm -f std/net/*.$(OBJEXT) @@ -1033,6 +1046,7 @@ clean-libtool: -rm -rf std/internal/math/.libs std/internal/math/_libs -rm -rf std/internal/test/.libs std/internal/test/_libs -rm -rf std/internal/windows/.libs std/internal/windows/_libs + -rm -rf std/logger/.libs std/logger/_libs -rm -rf std/math/.libs std/math/_libs -rm -rf std/net/.libs std/net/_libs -rm -rf std/range/.libs std/range/_libs @@ -1162,6 +1176,7 @@ distclean-generic: -rm -f std/internal/math/$(am__dirstamp) -rm -f std/internal/test/$(am__dirstamp) -rm -f std/internal/windows/$(am__dirstamp) + -rm -f std/logger/$(am__dirstamp) -rm -f std/math/$(am__dirstamp) -rm -f std/net/$(am__dirstamp) -rm -f std/range/$(am__dirstamp) diff --git a/libphobos/src/index.dd b/libphobos/src/index.dd index cbc173d..45c248e 100644 --- a/libphobos/src/index.dd +++ b/libphobos/src/index.dd @@ -459,6 +459,19 @@ $(BOOKTABLE , $(TDNW $(MREF core,simd)) $(TD SIMD intrinsics) ) + $(LEADINGROW Logging) + $(TR + $(TDNW + $(MREF std,logger)$(BR) + $(MREF std,logger,core)$(BR) + $(MREF std,logger,filelogger)$(BR) + $(MREF std,logger,multilogger)$(BR) + $(MREF std,logger,nulllogger)$(BR) + ) + $(TD + Logging. + ) + ) $(COMMENT $(LEADINGROW Undocumented modules (intentionally omitted).) @@ -509,18 +522,6 @@ $(COMMENT Deprecated modules. ) ) - $(TR - $(TDNW - $(MREF std,experimental,logger)$(BR) - $(MREF std,experimental,logger,core)$(BR) - $(MREF std,experimental,logger,filelogger)$(BR) - $(MREF std,experimental,logger,multilogger)$(BR) - $(MREF std,experimental,logger,nulllogger)$(BR) - ) - $(TD - Experimental modules. - ) - ) ) ) diff --git a/libphobos/src/std/algorithm/iteration.d b/libphobos/src/std/algorithm/iteration.d index 300a897..3e828ce 100644 --- a/libphobos/src/std/algorithm/iteration.d +++ b/libphobos/src/std/algorithm/iteration.d @@ -1798,7 +1798,7 @@ if (isInputRange!R) assert(equal(g3, [ tuple(1, 2u), tuple(2, 2u) ])); interface I {} - class C : I { override size_t toHash() const nothrow @safe { return 0; } } + static class C : I { override size_t toHash() const nothrow @safe { return 0; } } const C[] a4 = [new const C()]; auto g4 = a4.group!"a is b"; assert(g4.front[1] == 1); @@ -2255,25 +2255,26 @@ if (isForwardRange!Range) import std.algorithm.comparison : equal; size_t popCount = 0; - class RefFwdRange + static class RefFwdRange { int[] impl; + size_t* pcount; @safe nothrow: - this(int[] data) { impl = data; } + this(int[] data, size_t* pcount) { impl = data; this.pcount = pcount; } @property bool empty() { return impl.empty; } @property auto ref front() { return impl.front; } void popFront() { impl.popFront(); - popCount++; + (*pcount)++; } - @property auto save() { return new RefFwdRange(impl); } + @property auto save() { return new RefFwdRange(impl, pcount); } } static assert(isForwardRange!RefFwdRange); - auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9]); + auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9], &popCount); auto groups = testdata.chunkBy!((a,b) => (a % 2) == (b % 2)); auto outerSave1 = groups.save; @@ -6058,7 +6059,7 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) import std.algorithm.comparison : equal; // Test by-reference separator - class RefSep { + static class RefSep { @safe: string _impl; this(string s) { _impl = s; } diff --git a/libphobos/src/std/algorithm/searching.d b/libphobos/src/std/algorithm/searching.d index daa4b99..870b1b4 100644 --- a/libphobos/src/std/algorithm/searching.d +++ b/libphobos/src/std/algorithm/searching.d @@ -13,7 +13,7 @@ $(T2 any, `any!"a > 0"([1, 2, -3, -4])` returns `true` because at least one element is positive) $(T2 balancedParens, - `balancedParens("((1 + 1) / 2)")` returns `true` because the + `balancedParens("((1 + 1) / 2)", '(', ')')` returns `true` because the string has balanced parentheses.) $(T2 boyerMooreFinder, `find("hello world", boyerMooreFinder("or"))` returns `"orld"` diff --git a/libphobos/src/std/array.d b/libphobos/src/std/array.d index b86e0f9..f48602e 100644 --- a/libphobos/src/std/array.d +++ b/libphobos/src/std/array.d @@ -2297,7 +2297,7 @@ if (isInputRange!RoR && // https://issues.dlang.org/show_bug.cgi?id=10895 @safe unittest { - class A + static class A { string name; alias name this; @@ -4376,8 +4376,8 @@ unittest return app[]; } - class C {} - struct S { const(C) c; } + static class C {} + static struct S { const(C) c; } S[] s = [ S(new C) ]; auto t = fastCopy(s); // Does not compile diff --git a/libphobos/src/std/bigint.d b/libphobos/src/std/bigint.d index b2fcc071..33d0eae 100644 --- a/libphobos/src/std/bigint.d +++ b/libphobos/src/std/bigint.d @@ -1541,7 +1541,7 @@ Returns: number in upper case. */ -string toHex(const(BigInt) x) @safe +string toHex(const(BigInt) x) pure @safe { import std.array : appender; auto outbuff = appender!string(); diff --git a/libphobos/src/std/complex.d b/libphobos/src/std/complex.d index 5a15538..3b860fe 100644 --- a/libphobos/src/std/complex.d +++ b/libphobos/src/std/complex.d @@ -478,6 +478,20 @@ if (isFloatingPoint!T) } return this; } + + /** Returns a complex number instance that correponds in size and in ABI + to the associated C compiler's `_Complex` type. + */ + auto toNative() + { + import core.stdc.config : c_complex_float, c_complex_double, c_complex_real; + static if (is(T == float)) + return c_complex_float(re, im); + else static if (is(T == double)) + return c_complex_double(re, im); + else + return c_complex_real(re, im); + } } @safe pure nothrow unittest @@ -1910,3 +1924,14 @@ Complex!T pow(T)(const T x, Complex!T n) @trusted pure nothrow @nogc } }} } + +@safe pure nothrow @nogc unittest +{ + import std.meta : AliasSeq; + static foreach (T; AliasSeq!(float, double, real)) + {{ + auto c = Complex!T(123, 456); + auto n = c.toNative(); + assert(c.re == n.re && c.im == n.im); + }} +} diff --git a/libphobos/src/std/container/rbtree.d b/libphobos/src/std/container/rbtree.d index 622dee4..9bd8d27 100644 --- a/libphobos/src/std/container/rbtree.d +++ b/libphobos/src/std/container/rbtree.d @@ -2055,7 +2055,7 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init } //Combinations not in examples. -@safe pure unittest +@system pure unittest { auto rbt1 = redBlackTree!(true, string)("hello", "hello"); auto rbt2 = redBlackTree!((a, b){return a < b;}, double)(5.1, 2.3); diff --git a/libphobos/src/std/experimental/logger/core.d b/libphobos/src/std/experimental/logger/core.d index f3c6932..a30ae58 100644 --- a/libphobos/src/std/experimental/logger/core.d +++ b/libphobos/src/std/experimental/logger/core.d @@ -1,3049 +1,13 @@ -// Written in the D programming language. /** -Source: $(PHOBOSSRC std/experimental/logger/core.d) -*/ -module std.experimental.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.experimental.logger.filelogger; - -/** This functions is used at runtime to determine if a `LogLevel` is -active. The same previously defined version statements are used to disable -certain levels. Again the version statements are associated with a compile -unit and can therefore not disable logging in other compile units. -pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc -*/ -bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL, - LogLevel globalLL, lazy bool condition = true) @safe -{ - return ll >= globalLL - && ll >= loggerLL - && ll != LogLevel.off - && globalLL != LogLevel.off - && loggerLL != LogLevel.off - && condition; -} - -/* This function formates a `SysTime` into an `OutputRange`. - -The `SysTime` is formatted similar to -$(LREF std.datatime.DateTime.toISOExtString) except the fractional second part. -The fractional second part is in milliseconds and is always 3 digits. -*/ -void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time) -if (isOutputRange!(OutputRange,string)) -{ - import std.format.write : formattedWrite; - - const auto dt = cast(DateTime) time; - const auto fsec = time.fracSecs.total!"msecs"; - - formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", - dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, - fsec); -} - -/** This function logs data. - -In order for the data to be processed, the `LogLevel` of the log call must -be greater or equal to the `LogLevel` of the `sharedLog` and the -`defaultLogLevel`; additionally the condition passed must be `true`. - -Params: - ll = The `LogLevel` used by this log call. - condition = The condition must be `true` for the data to be logged. - args = The data that should be logged. - -Example: --------------------- -log(LogLevel.warning, true, "Hello World", 3.1415); --------------------- -*/ -void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, - lazy bool condition, lazy A args) -if (args.length != 1) -{ - stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) - (ll, condition, args); -} - -/// Ditto -void log(T, string moduleName = __MODULE__)(const LogLevel ll, - lazy bool condition, lazy T arg, int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__) -{ - stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName, - prettyFuncName); -} - -/** This function logs data. - -In order for the data to be processed the `LogLevel` of the log call must -be greater or equal to the `LogLevel` of the `sharedLog`. - -Params: - ll = The `LogLevel` used by this log call. - args = The data that should be logged. - -Example: --------------------- -log(LogLevel.warning, "Hello World", 3.1415); --------------------- -*/ -void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) -if (args.length > 1 && !is(Unqual!(A[0]) : bool)) -{ - stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) - (ll, args); -} - -/// Ditto -void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg, - int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__) -{ - stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName, - moduleName); -} - -/** This function logs data. - -In order for the data to be processed the `LogLevel` of the -`sharedLog` must be greater or equal to the `defaultLogLevel` -add the condition passed must be `true`. - -Params: - condition = The condition must be `true` for the data to be logged. - args = The data that should be logged. - -Example: --------------------- -log(true, "Hello World", 3.1415); --------------------- -*/ -void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) -if (args.length != 1) -{ - stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) - (stdThreadLocalLog.logLevel, condition, args); -} - -/// Ditto -void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg, - int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__) -{ - stdThreadLocalLog.log!(T,moduleName)(stdThreadLocalLog.logLevel, - condition, arg, line, file, funcName, prettyFuncName); -} - -/** This function logs data. - -In order for the data to be processed the `LogLevel` of the -`sharedLog` must be greater or equal to the `defaultLogLevel`. - -Params: - args = The data that should be logged. - -Example: --------------------- -log("Hello World", 3.1415); --------------------- -*/ -void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy A args) -if ((args.length > 1 && !is(Unqual!(A[0]) : bool) - && !is(Unqual!(A[0]) == LogLevel)) - || args.length == 0) -{ - stdThreadLocalLog.log!(line, file, funcName, - prettyFuncName, moduleName)(stdThreadLocalLog.logLevel, args); -} - -void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__) -{ - stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file, - funcName, prettyFuncName, moduleName); -} - -/** This function logs data in a `printf`-style manner. - -In order for the data to be processed the `LogLevel` of the log call must -be greater or equal to the `LogLevel` of the `sharedLog` and the -`defaultLogLevel` additionally the condition passed must be `true`. - -Params: - ll = The `LogLevel` used by this log call. - condition = The condition must be `true` for the data to be logged. - msg = The `printf`-style string. - args = The data that should be logged. - -Example: --------------------- -logf(LogLevel.warning, true, "Hello World %f", 3.1415); --------------------- -*/ -void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, - lazy bool condition, lazy string msg, lazy A args) -{ - stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) - (ll, condition, msg, args); -} - -/** This function logs data in a `printf`-style manner. - -In order for the data to be processed the `LogLevel` of the log call must -be greater or equal to the `LogLevel` of the `sharedLog` and the -`defaultLogLevel`. - -Params: - ll = The `LogLevel` used by this log call. - msg = The `printf`-style string. - args = The data that should be logged. - -Example: --------------------- -logf(LogLevel.warning, "Hello World %f", 3.1415); --------------------- -*/ -void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, lazy string msg, - lazy A args) -{ - stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) - (ll, msg, args); -} - -/** This function logs data in a `printf`-style manner. - -In order for the data to be processed the `LogLevel` of the log call must -be greater or equal to the `defaultLogLevel` additionally the condition -passed must be `true`. - -Params: - condition = The condition must be `true` for the data to be logged. - msg = The `printf`-style string. - args = The data that should be logged. - -Example: --------------------- -logf(true, "Hello World %f", 3.1415); --------------------- -*/ -void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, - lazy string msg, lazy A args) -{ - stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) - (stdThreadLocalLog.logLevel, condition, msg, args); -} - -/** This function logs data in a `printf`-style manner. - -In order for the data to be processed the `LogLevel` of the log call must -be greater or equal to the `defaultLogLevel`. - -Params: - msg = The `printf`-style string. - args = The data that should be logged. - -Example: --------------------- -logf("Hello World %f", 3.1415); --------------------- -*/ -void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) -{ - stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName) - (stdThreadLocalLog.logLevel, msg, args); -} - -/** This template provides the global log functions with the `LogLevel` -is encoded in the function name. - -The aliases following this template create the public names of these log -functions. -*/ -template defaultLogFunction(LogLevel ll) -{ - void defaultLogFunction(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy A args) - if ((args.length > 0 && !is(Unqual!(A[0]) : bool)) || args.length == 0) - { - stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, - prettyFuncName, moduleName)(args); - } - - void defaultLogFunction(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) - { - stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, - prettyFuncName, moduleName)(condition, args); - } -} - -/** This function logs data to the `stdThreadLocalLog`, optionally depending -on a condition. - -In order for the resulting log message to be logged the `LogLevel` must -be greater or equal than the `LogLevel` of the `stdThreadLocalLog` and -must be greater or equal than the global `LogLevel`. -Additionally the `LogLevel` must be greater or equal than the `LogLevel` -of the `stdSharedLogger`. -If a condition is given, it must evaluate to `true`. - -Params: - condition = The condition must be `true` for the data to be logged. - args = The data that should be logged. - -Example: --------------------- -trace(1337, "is number"); -info(1337, "is number"); -error(1337, "is number"); -critical(1337, "is number"); -fatal(1337, "is number"); -trace(true, 1337, "is number"); -info(false, 1337, "is number"); -error(true, 1337, "is number"); -critical(false, 1337, "is number"); -fatal(true, 1337, "is number"); --------------------- -*/ -alias trace = defaultLogFunction!(LogLevel.trace); -/// Ditto -alias info = defaultLogFunction!(LogLevel.info); -/// Ditto -alias warning = defaultLogFunction!(LogLevel.warning); -/// Ditto -alias error = defaultLogFunction!(LogLevel.error); -/// Ditto -alias critical = defaultLogFunction!(LogLevel.critical); -/// Ditto -alias fatal = defaultLogFunction!(LogLevel.fatal); - -/** This template provides the global `printf`-style log functions with -the `LogLevel` is encoded in the function name. - -The aliases following this template create the public names of the log -functions. -*/ -template defaultLogFunctionf(LogLevel ll) -{ - void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) - { - stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, - prettyFuncName, moduleName)(msg, args); - } - - void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, - lazy string msg, lazy A args) - { - stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, - prettyFuncName, moduleName)(condition, msg, args); - } -} - -/** This function logs data to the `sharedLog` in a `printf`-style -manner. - -In order for the resulting log message to be logged the `LogLevel` must -be greater or equal than the `LogLevel` of the `sharedLog` and -must be greater or equal than the global `LogLevel`. -Additionally the `LogLevel` must be greater or equal than the `LogLevel` -of the `stdSharedLogger`. - -Params: - msg = The `printf`-style string. - args = The data that should be logged. - -Example: --------------------- -tracef("is number %d", 1); -infof("is number %d", 2); -errorf("is number %d", 3); -criticalf("is number %d", 4); -fatalf("is number %d", 5); --------------------- - -The second version of the function logs data to the `sharedLog` in a $(D -printf)-style manner. - -In order for the resulting log message to be logged the `LogLevel` must -be greater or equal than the `LogLevel` of the `sharedLog` and -must be greater or equal than the global `LogLevel`. -Additionally the `LogLevel` must be greater or equal than the `LogLevel` -of the `stdSharedLogger`. - -Params: - condition = The condition must be `true` for the data to be logged. - msg = The `printf`-style string. - args = The data that should be logged. - -Example: --------------------- -tracef(false, "is number %d", 1); -infof(false, "is number %d", 2); -errorf(true, "is number %d", 3); -criticalf(true, "is number %d", 4); -fatalf(someFunct(), "is number %d", 5); --------------------- -*/ -alias tracef = defaultLogFunctionf!(LogLevel.trace); -/// Ditto -alias infof = defaultLogFunctionf!(LogLevel.info); -/// Ditto -alias warningf = defaultLogFunctionf!(LogLevel.warning); -/// Ditto -alias errorf = defaultLogFunctionf!(LogLevel.error); -/// Ditto -alias criticalf = defaultLogFunctionf!(LogLevel.critical); -/// Ditto -alias fatalf = defaultLogFunctionf!(LogLevel.fatal); - -private struct MsgRange -{ - import std.traits : isSomeString, isSomeChar; - - private Logger log; - - this(Logger log) @safe - { - this.log = log; - } - - void put(T)(T msg) @safe - if (isSomeString!T) - { - log.logMsgPart(msg); - } - - void put(dchar elem) @safe - { - import std.utf : encode; - char[4] buffer; - size_t len = encode(buffer, elem); - log.logMsgPart(buffer[0 .. len]); - } -} - -private void formatString(A...)(MsgRange oRange, A args) -{ - import std.format.write : formattedWrite; - - foreach (arg; args) - { - formattedWrite(oRange, "%s", arg); - } -} - -@system unittest -{ - void dummy() @safe - { - auto tl = new TestLogger(); - auto dst = MsgRange(tl); - formatString(dst, "aaa", "bbb"); - } - - dummy(); -} - -/** -There are eight usable logging level. These level are $(I all), $(I trace), -$(I info), $(I warning), $(I error), $(I critical), $(I fatal), and $(I off). -If a log function with `LogLevel.fatal` is called the shutdown handler of -that logger is called. -*/ -enum LogLevel : ubyte -{ - all = 1, /** Lowest possible assignable `LogLevel`. */ - trace = 32, /** `LogLevel` for tracing the execution of the program. */ - info = 64, /** This level is used to display information about the - program. */ - warning = 96, /** warnings about the program should be displayed with this - level. */ - error = 128, /** Information about errors should be logged with this - level.*/ - critical = 160, /** Messages that inform about critical errors should be - logged with this level. */ - fatal = 192, /** Log messages that describe fatal errors should use this - level. */ - off = ubyte.max /** Highest possible `LogLevel`. */ -} - -/** This class is the base of every logger. In order to create a new kind of -logger a deriving class needs to implement the `writeLogMsg` method. By -default this is not thread-safe. - -It is also possible to `override` the three methods `beginLogMsg`, -`logMsgPart` and `finishLogMsg` together, this option gives more -flexibility. -*/ -abstract class Logger -{ - import std.array : appender, Appender; - import std.concurrency : thisTid, Tid; - - /** LogEntry is a aggregation combining all information associated - with a log message. This aggregation will be passed to the method - writeLogMsg. - */ - protected struct LogEntry - { - /// the filename the log function was called from - string file; - /// the line number the log function was called from - int line; - /// the name of the function the log function was called from - string funcName; - /// the pretty formatted name of the function the log function was - /// called from - string prettyFuncName; - /// the name of the module the log message is coming from - string moduleName; - /// the `LogLevel` associated with the log message - LogLevel logLevel; - /// thread id of the log message - Tid threadId; - /// the time the message was logged - SysTime timestamp; - /// the message of the log message - string msg; - /// A refernce to the `Logger` used to create this `LogEntry` - Logger logger; - } - - /** - Every subclass of `Logger` has to call this constructor from their - constructor. It sets the `LogLevel`, and creates a fatal handler. The fatal - handler will throw an `Error` if a log call is made with level - `LogLevel.fatal`. - - Params: - lv = `LogLevel` to use for this `Logger` instance. - */ - this(this This)(LogLevel lv) - { - this.logLevel_ = lv; - this.fatalHandler_ = delegate() { - throw new Error("A fatal log message was logged"); - }; - - this.mutex = new typeof(mutex)(); - } - - /** A custom logger must implement this method in order to work in a - `MultiLogger` and `ArrayLogger`. - - Params: - payload = All information associated with call to log function. - - See_Also: beginLogMsg, logMsgPart, finishLogMsg - */ - abstract protected void writeLogMsg(ref LogEntry payload) @safe; - - /* The default implementation will use an `std.array.appender` - internally to construct the message string. This means dynamic, - GC memory allocation. A logger can avoid this allocation by - reimplementing `beginLogMsg`, `logMsgPart` and `finishLogMsg`. - `beginLogMsg` is always called first, followed by any number of calls - to `logMsgPart` and one call to `finishLogMsg`. - - As an example for such a custom `Logger` compare this: - ---------------- - class CLogger : Logger - { - override void beginLogMsg(string file, int line, string funcName, - string prettyFuncName, string moduleName, LogLevel logLevel, - Tid threadId, SysTime timestamp) - { - ... logic here - } - - override void logMsgPart(const(char)[] msg) - { - ... logic here - } - - override void finishLogMsg() - { - ... logic here - } - - void writeLogMsg(ref LogEntry payload) - { - this.beginLogMsg(payload.file, payload.line, payload.funcName, - payload.prettyFuncName, payload.moduleName, payload.logLevel, - payload.threadId, payload.timestamp, payload.logger); - - this.logMsgPart(payload.msg); - this.finishLogMsg(); - } - } - ---------------- - */ - protected void beginLogMsg(string file, int line, string funcName, - string prettyFuncName, string moduleName, LogLevel logLevel, - Tid threadId, SysTime timestamp, Logger logger) - @safe - { - msgAppender = appender!string(); - header = LogEntry(file, line, funcName, prettyFuncName, - moduleName, logLevel, threadId, timestamp, null, logger); - } - - /** Logs a part of the log message. */ - protected void logMsgPart(scope const(char)[] msg) @safe - { - msgAppender.put(msg); - } - - /** Signals that the message has been written and no more calls to - `logMsgPart` follow. */ - protected void finishLogMsg() @safe - { - header.msg = msgAppender.data; - this.writeLogMsg(header); - } - - /** The `LogLevel` determines if the log call are processed or dropped - by the `Logger`. In order for the log call to be processed the - `LogLevel` of the log call must be greater or equal to the `LogLevel` - of the `logger`. - - These two methods set and get the `LogLevel` of the used `Logger`. - - Example: - ----------- - auto f = new FileLogger(stdout); - f.logLevel = LogLevel.info; - assert(f.logLevel == LogLevel.info); - ----------- - */ - @property final LogLevel logLevel() const pure @safe @nogc - { - return trustedLoad(this.logLevel_); - } - - /// Ditto - @property final void logLevel(const LogLevel lv) @safe @nogc - { - atomicStore(this.logLevel_, lv); - } - - /** This `delegate` is called in case a log message with - `LogLevel.fatal` gets logged. - - By default an `Error` will be thrown. - */ - @property final void delegate() fatalHandler() @safe @nogc - { - synchronized (mutex) return this.fatalHandler_; - } - - /// Ditto - @property final void fatalHandler(void delegate() @safe fh) @safe @nogc - { - synchronized (mutex) this.fatalHandler_ = fh; - } - - /** This method allows forwarding log entries from one logger to another. - - `forwardMsg` will ensure proper synchronization and then call - `writeLogMsg`. This is an API for implementing your own loggers and - should not be called by normal user code. A notable difference from other - logging functions is that the `globalLogLevel` wont be evaluated again - since it is assumed that the caller already checked that. - */ - void forwardMsg(ref LogEntry payload) @trusted - { - if (isLoggingEnabled(payload.logLevel, this.logLevel_, - globalLogLevel)) - { - this.writeLogMsg(payload); - - if (payload.logLevel == LogLevel.fatal) - this.fatalHandler_(); - } - } - - /** This template provides the log functions for the `Logger` `class` - with the `LogLevel` encoded in the function name. - - For further information see the the two functions defined inside of this - template. - - The aliases following this template create the public names of these log - functions. - */ - template memLogFunctions(LogLevel ll) - { - /** This function logs data to the used `Logger`. - - In order for the resulting log message to be logged the `LogLevel` - must be greater or equal than the `LogLevel` of the used `Logger` - and must be greater or equal than the global `LogLevel`. - - Params: - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.trace(1337, "is number"); - s.info(1337, "is number"); - s.error(1337, "is number"); - s.critical(1337, "is number"); - s.fatal(1337, "is number"); - -------------------- - */ - void logImpl(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy A args) - if (args.length == 0 || (args.length > 0 && !is(A[0] : bool))) - { - synchronized (mutex) - { - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - static if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` depending on a - condition. - - In order for the resulting log message to be logged the `LogLevel` must - be greater or equal than the `LogLevel` of the used `Logger` and - must be greater or equal than the global `LogLevel` additionally the - condition passed must be `true`. - - Params: - condition = The condition must be `true` for the data to be logged. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.trace(true, 1337, "is number"); - s.info(false, 1337, "is number"); - s.error(true, 1337, "is number"); - s.critical(false, 1337, "is number"); - s.fatal(true, 1337, "is number"); - -------------------- - */ - void logImpl(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, - lazy A args) - { - synchronized (mutex) - { - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, - condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - static if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` in a - `printf`-style manner. - - In order for the resulting log message to be logged the `LogLevel` - must be greater or equal than the `LogLevel` of the used `Logger` - and must be greater or equal than the global `LogLevel` additionally - the passed condition must be `true`. - - Params: - condition = The condition must be `true` for the data to be logged. - msg = The `printf`-style string. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stderr); - s.tracef(true, "is number %d", 1); - s.infof(true, "is number %d", 2); - s.errorf(false, "is number %d", 3); - s.criticalf(someFunc(), "is number %d", 4); - s.fatalf(true, "is number %d", 5); - -------------------- - */ - void logImplf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, - lazy string msg, lazy A args) - { - synchronized (mutex) - { - import std.format.write : formattedWrite; - - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, - condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formattedWrite(writer, msg, args); - - this.finishLogMsg(); - - static if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` in a - `printf`-style manner. - - In order for the resulting log message to be logged the `LogLevel` must - be greater or equal than the `LogLevel` of the used `Logger` and - must be greater or equal than the global `LogLevel`. - - Params: - msg = The `printf`-style string. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stderr); - s.tracef("is number %d", 1); - s.infof("is number %d", 2); - s.errorf("is number %d", 3); - s.criticalf("is number %d", 4); - s.fatalf("is number %d", 5); - -------------------- - */ - void logImplf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) - { - synchronized (mutex) - { - import std.format.write : formattedWrite; - - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formattedWrite(writer, msg, args); - - this.finishLogMsg(); - - static if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - } - - /// Ditto - alias trace = memLogFunctions!(LogLevel.trace).logImpl; - /// Ditto - alias tracef = memLogFunctions!(LogLevel.trace).logImplf; - /// Ditto - alias info = memLogFunctions!(LogLevel.info).logImpl; - /// Ditto - alias infof = memLogFunctions!(LogLevel.info).logImplf; - /// Ditto - alias warning = memLogFunctions!(LogLevel.warning).logImpl; - /// Ditto - alias warningf = memLogFunctions!(LogLevel.warning).logImplf; - /// Ditto - alias error = memLogFunctions!(LogLevel.error).logImpl; - /// Ditto - alias errorf = memLogFunctions!(LogLevel.error).logImplf; - /// Ditto - alias critical = memLogFunctions!(LogLevel.critical).logImpl; - /// Ditto - alias criticalf = memLogFunctions!(LogLevel.critical).logImplf; - /// Ditto - alias fatal = memLogFunctions!(LogLevel.fatal).logImpl; - /// Ditto - alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf; - - /** This method logs data with the `LogLevel` of the used `Logger`. - - This method takes a `bool` as first argument. In order for the - data to be processed the `bool` must be `true` and the `LogLevel` - of the Logger must be greater or equal to the global `LogLevel`. - - Params: - args = The data that should be logged. - condition = The condition must be `true` for the data to be logged. - args = The data that is to be logged. - - Returns: The logger used by the logging function as reference. - - Example: - -------------------- - auto l = new StdioLogger(); - l.log(1337); - -------------------- - */ - void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, - lazy bool condition, lazy A args) - if (args.length != 1) - { - synchronized (mutex) - { - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /// Ditto - void log(T, string moduleName = __MODULE__)(const LogLevel ll, - lazy bool condition, lazy T args, int line = __LINE__, - string file = __FILE__, string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__) - { - synchronized (mutex) - { - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` with a specific - `LogLevel`. - - In order for the resulting log message to be logged the `LogLevel` - must be greater or equal than the `LogLevel` of the used `Logger` - and must be greater or equal than the global `LogLevel`. - - Params: - ll = The specific `LogLevel` used for logging the log message. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.log(LogLevel.trace, 1337, "is number"); - s.log(LogLevel.info, 1337, "is number"); - s.log(LogLevel.warning, 1337, "is number"); - s.log(LogLevel.error, 1337, "is number"); - s.log(LogLevel.fatal, 1337, "is number"); - -------------------- - */ - void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) - if ((args.length > 1 && !is(Unqual!(A[0]) : bool)) || args.length == 0) - { - synchronized (mutex) - { - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /// Ditto - void log(T)(const LogLevel ll, lazy T args, int line = __LINE__, - string file = __FILE__, string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__) - { - synchronized (mutex) - { - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` depending on a - explicitly passed condition with the `LogLevel` of the used - `Logger`. - - In order for the resulting log message to be logged the `LogLevel` - of the used `Logger` must be greater or equal than the global - `LogLevel` and the condition must be `true`. - - Params: - condition = The condition must be `true` for the data to be logged. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.log(true, 1337, "is number"); - s.log(true, 1337, "is number"); - s.log(true, 1337, "is number"); - s.log(false, 1337, "is number"); - s.log(false, 1337, "is number"); - -------------------- - */ - void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) - if (args.length != 1) - { - synchronized (mutex) - { - if (isLoggingEnabled(this.logLevel_, this.logLevel_, - globalLogLevel, condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, this.logLevel_, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - if (this.logLevel_ == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /// Ditto - void log(T)(lazy bool condition, lazy T args, int line = __LINE__, - string file = __FILE__, string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__) - { - synchronized (mutex) - { - if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, - condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, this.logLevel_, thisTid, Clock.currTime, this); - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - if (this.logLevel_ == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` with the `LogLevel` - of the used `Logger`. - - In order for the resulting log message to be logged the `LogLevel` - of the used `Logger` must be greater or equal than the global - `LogLevel`. - - Params: - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.log(1337, "is number"); - s.log(info, 1337, "is number"); - s.log(1337, "is number"); - s.log(1337, "is number"); - s.log(1337, "is number"); - -------------------- - */ - void log(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy A args) - if ((args.length > 1 - && !is(Unqual!(A[0]) : bool) - && !is(immutable A[0] == immutable LogLevel)) - || args.length == 0) - { - synchronized (mutex) - { - if (isLoggingEnabled(this.logLevel_, this.logLevel_, - globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, this.logLevel_, thisTid, Clock.currTime, this); - auto writer = MsgRange(this); - formatString(writer, args); - - this.finishLogMsg(); - - if (this.logLevel_ == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /// Ditto - void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__) - { - synchronized (mutex) - { - if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, this.logLevel_, thisTid, Clock.currTime, this); - auto writer = MsgRange(this); - formatString(writer, arg); - - this.finishLogMsg(); - - if (this.logLevel_ == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` with a specific - `LogLevel` and depending on a condition in a `printf`-style manner. - - In order for the resulting log message to be logged the `LogLevel` - must be greater or equal than the `LogLevel` of the used `Logger` - and must be greater or equal than the global `LogLevel` and the - condition must be `true`. - - Params: - ll = The specific `LogLevel` used for logging the log message. - condition = The condition must be `true` for the data to be logged. - msg = The format string used for this log call. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.logf(LogLevel.trace, true ,"%d %s", 1337, "is number"); - s.logf(LogLevel.info, true ,"%d %s", 1337, "is number"); - s.logf(LogLevel.warning, true ,"%d %s", 1337, "is number"); - s.logf(LogLevel.error, false ,"%d %s", 1337, "is number"); - s.logf(LogLevel.fatal, true ,"%d %s", 1337, "is number"); - -------------------- - */ - void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, - lazy bool condition, lazy string msg, lazy A args) - { - synchronized (mutex) - { - import std.format.write : formattedWrite; - - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formattedWrite(writer, msg, args); - - this.finishLogMsg(); - - if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` with a specific - `LogLevel` in a `printf`-style manner. - - In order for the resulting log message to be logged the `LogLevel` - must be greater or equal than the `LogLevel` of the used `Logger` - and must be greater or equal than the global `LogLevel`. - - Params: - ll = The specific `LogLevel` used for logging the log message. - msg = The format string used for this log call. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.logf(LogLevel.trace, "%d %s", 1337, "is number"); - s.logf(LogLevel.info, "%d %s", 1337, "is number"); - s.logf(LogLevel.warning, "%d %s", 1337, "is number"); - s.logf(LogLevel.error, "%d %s", 1337, "is number"); - s.logf(LogLevel.fatal, "%d %s", 1337, "is number"); - -------------------- - */ - void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(const LogLevel ll, - lazy string msg, lazy A args) - { - synchronized (mutex) - { - import std.format.write : formattedWrite; - - if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, ll, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formattedWrite(writer, msg, args); - - this.finishLogMsg(); - - if (ll == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This function logs data to the used `Logger` depending on a - condition with the `LogLevel` of the used `Logger` in a - `printf`-style manner. - - In order for the resulting log message to be logged the `LogLevel` - of the used `Logger` must be greater or equal than the global - `LogLevel` and the condition must be `true`. - - Params: - condition = The condition must be `true` for the data to be logged. - msg = The format string used for this log call. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.logf(true ,"%d %s", 1337, "is number"); - s.logf(true ,"%d %s", 1337, "is number"); - s.logf(true ,"%d %s", 1337, "is number"); - s.logf(false ,"%d %s", 1337, "is number"); - s.logf(true ,"%d %s", 1337, "is number"); - -------------------- - */ - void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy bool condition, - lazy string msg, lazy A args) - { - synchronized (mutex) - { - import std.format.write : formattedWrite; - - if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, - condition)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, this.logLevel_, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formattedWrite(writer, msg, args); - - this.finishLogMsg(); - - if (this.logLevel_ == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - /** This method logs data to the used `Logger` with the `LogLevel` - of the this `Logger` in a `printf`-style manner. - - In order for the data to be processed the `LogLevel` of the `Logger` - must be greater or equal to the global `LogLevel`. - - Params: - msg = The format string used for this log call. - args = The data that should be logged. - - Example: - -------------------- - auto s = new FileLogger(stdout); - s.logf("%d %s", 1337, "is number"); - s.logf("%d %s", 1337, "is number"); - s.logf("%d %s", 1337, "is number"); - s.logf("%d %s", 1337, "is number"); - s.logf("%d %s", 1337, "is number"); - -------------------- - */ - void logf(int line = __LINE__, string file = __FILE__, - string funcName = __FUNCTION__, - string prettyFuncName = __PRETTY_FUNCTION__, - string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) - { - synchronized (mutex) - { - import std.format.write : formattedWrite; - - if (isLoggingEnabled(this.logLevel_, this.logLevel_, - globalLogLevel)) - { - this.beginLogMsg(file, line, funcName, prettyFuncName, - moduleName, this.logLevel_, thisTid, Clock.currTime, this); - - auto writer = MsgRange(this); - formattedWrite(writer, msg, args); - - this.finishLogMsg(); - - if (this.logLevel_ == LogLevel.fatal) - this.fatalHandler_(); - } - } - } - - private void delegate() @safe fatalHandler_; - private shared LogLevel logLevel_ = LogLevel.info; - private Mutex mutex; - - protected Appender!string msgAppender; - protected LogEntry header; -} - -// Thread Global - -private shared Logger stdSharedDefaultLogger; -private shared Logger stdSharedLogger; -private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all; - -/* This method returns the global default Logger. - * Marked @trusted because of excessive reliance on __gshared data + * This module is now deprecated, use $(MREF std, logger, core) + * instead. + * + * Copyright: Copyright The D Language Foundation 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Source: $(PHOBOSSRC std/experimental/logger/core.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) */ -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 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 the used `Logger` is thread-safe. -The default `Logger` is thread-safe. -------------- -if (sharedLog !is myLogger) - sharedLog = new 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)); -} - -/** This methods get and set the global `LogLevel`. - -Every log message with a `LogLevel` lower as 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 threads 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; - } - } - } - - sharedLog = new shared RaceLogger; - scope(exit) { sharedLog = null; } - () @trusted { new Thread(() { log("foo"); }).start(); }(); - log("bar"); -} - -/** 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.experimental.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.experimental.logger.core.testFuncNames", tl1.func); - assert(tl1.prettyFunc == - "void std.experimental.logger.core.testFuncNames(Logger logger) @safe", - tl1.prettyFunc); - assert(tl1.msg == "I'm here", tl1.msg); -} - -@safe unittest -{ - 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.experimental.logger.multilogger : MultiLogger; - - auto tl1 = new TestLogger; - auto tl2 = new TestLogger; - - auto ml = new MultiLogger(); - ml.insertLogger("one", tl1); - ml.insertLogger("two", tl2); - - string msg = "Hello Logger World"; - ml.log(msg); - int lineNumber = __LINE__ - 1; - assert(tl1.msg == msg); - assert(tl1.line == lineNumber); - assert(tl2.msg == msg); - assert(tl2.line == lineNumber); - - ml.removeLogger("one"); - ml.removeLogger("two"); - auto n = ml.removeLogger("one"); - assert(n is null); -} - -@safe unittest -{ - bool errorThrown = false; - auto tl = new TestLogger; - auto dele = delegate() { - errorThrown = true; - }; - tl.fatalHandler = dele; - tl.fatal(); - assert(errorThrown); -} - -@safe unittest -{ - 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")); - 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.experimental.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(); -} +module std.experimental.logger.core; +public import std.logger.core; diff --git a/libphobos/src/std/experimental/logger/filelogger.d b/libphobos/src/std/experimental/logger/filelogger.d index 457012e..3205a25 100644 --- a/libphobos/src/std/experimental/logger/filelogger.d +++ b/libphobos/src/std/experimental/logger/filelogger.d @@ -1,272 +1,13 @@ -// Written in the D programming language. /** -Source: $(PHOBOSSRC std/experimental/logger/filelogger.d) -*/ + * This module is now deprecated, use $(MREF std, logger, filelogger) + * instead. + * + * Copyright: Copyright The D Language Foundation 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Source: $(PHOBOSSRC std/experimental/logger/filelogger.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ module std.experimental.logger.filelogger; - -import std.experimental.logger.core; -import std.stdio; - -import std.typecons : Flag; - -/** An option to create $(LREF FileLogger) directory if it is non-existent. -*/ -alias CreateFolder = Flag!"CreateFolder"; - -/** This `Logger` implementation writes log messages to the associated -file. The name of the file has to be passed on construction time. If the file -is already present new log messages will be append at its end. -*/ -class FileLogger : Logger -{ - import std.concurrency : Tid; - import std.datetime.systime : SysTime; - import std.format.write : formattedWrite; - - /** A constructor for the `FileLogger` Logger. - - Params: - fn = The filename of the output file of the `FileLogger`. If that - file can not be opened for writting an exception will be thrown. - lv = The `LogLevel` for the `FileLogger`. By default the - - Example: - ------------- - auto l1 = new FileLogger("logFile"); - auto l2 = new FileLogger("logFile", LogLevel.fatal); - auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes); - ------------- - */ - this(const string fn, const LogLevel lv = LogLevel.all) @safe - { - this(fn, lv, CreateFolder.yes); - } - - /** A constructor for the `FileLogger` Logger that takes a reference to - a `File`. - - The `File` passed must be open for all the log call to the - `FileLogger`. If the `File` gets closed, using the `FileLogger` - for logging will result in undefined behaviour. - - Params: - fn = The file used for logging. - lv = The `LogLevel` for the `FileLogger`. By default the - `LogLevel` for `FileLogger` is `LogLevel.all`. - createFileNameFolder = if yes and fn contains a folder name, this - folder will be created. - - Example: - ------------- - auto file = File("logFile.log", "w"); - auto l1 = new FileLogger(file); - auto l2 = new FileLogger(file, LogLevel.fatal); - ------------- - */ - this(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe - { - import std.file : exists, mkdirRecurse; - import std.path : dirName; - import std.conv : text; - - super(lv); - this.filename = fn; - - if (createFileNameFolder) - { - auto d = dirName(this.filename); - mkdirRecurse(d); - assert(exists(d), text("The folder the FileLogger should have", - " created in '", d,"' could not be created.")); - } - - this.file_.open(this.filename, "a"); - } - - /** A constructor for the `FileLogger` Logger that takes a reference to - a `File`. - - The `File` passed must be open for all the log call to the - `FileLogger`. If the `File` gets closed, using the `FileLogger` - for logging will result in undefined behaviour. - - Params: - file = The file used for logging. - lv = The `LogLevel` for the `FileLogger`. By default the - `LogLevel` for `FileLogger` is `LogLevel.all`. - - Example: - ------------- - auto file = File("logFile.log", "w"); - auto l1 = new FileLogger(file); - auto l2 = new FileLogger(file, LogLevel.fatal); - ------------- - */ - this(File file, const LogLevel lv = LogLevel.all) @safe - { - super(lv); - this.file_ = file; - } - - /** If the `FileLogger` is managing the `File` it logs to, this - method will return a reference to this File. - */ - @property File file() @safe - { - return this.file_; - } - - /* This method overrides the base class method in order to log to a file - without requiring heap allocated memory. Additionally, the `FileLogger` - local mutex is logged to serialize the log calls. - */ - override protected void beginLogMsg(string file, int line, string funcName, - string prettyFuncName, string moduleName, LogLevel logLevel, - Tid threadId, SysTime timestamp, Logger logger) - @safe - { - import std.string : lastIndexOf; - ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; - ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; - - auto lt = this.file_.lockingTextWriter(); - systimeToISOString(lt, timestamp); - import std.conv : to; - formattedWrite(lt, " [%s] %s:%u:%s ", logLevel.to!string, - file[fnIdx .. $], line, funcName[funIdx .. $]); - } - - /* This methods overrides the base class method and writes the parts of - the log call directly to the file. - */ - override protected void logMsgPart(scope const(char)[] msg) - { - formattedWrite(this.file_.lockingTextWriter(), "%s", msg); - } - - /* This methods overrides the base class method and finalizes the active - log call. This requires flushing the `File` and releasing the - `FileLogger` local mutex. - */ - override protected void finishLogMsg() - { - this.file_.lockingTextWriter().put("\n"); - this.file_.flush(); - } - - /* This methods overrides the base class method and delegates the - `LogEntry` data to the actual implementation. - */ - override protected void writeLogMsg(ref LogEntry payload) - { - this.beginLogMsg(payload.file, payload.line, payload.funcName, - payload.prettyFuncName, payload.moduleName, payload.logLevel, - payload.threadId, payload.timestamp, payload.logger); - this.logMsgPart(payload.msg); - this.finishLogMsg(); - } - - /** If the `FileLogger` was constructed with a filename, this method - returns this filename. Otherwise an empty `string` is returned. - */ - string getFilename() - { - return this.filename; - } - - /** The `File` log messages are written to. */ - protected File file_; - - /** The filename of the `File` log messages are written to. */ - protected string filename; -} - -@system unittest -{ - import std.array : empty; - import std.file : deleteme, remove; - import std.string : indexOf; - - string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; - auto l = new FileLogger(filename); - - scope(exit) - { - remove(filename); - } - - string notWritten = "this should not be written to file"; - string written = "this should be written to file"; - - l.logLevel = LogLevel.critical; - l.log(LogLevel.warning, notWritten); - l.log(LogLevel.critical, written); - destroy(l); - - auto file = File(filename, "r"); - string readLine = file.readln(); - assert(readLine.indexOf(written) != -1, readLine); - readLine = file.readln(); - assert(readLine.indexOf(notWritten) == -1, readLine); -} - -@safe unittest -{ - import std.file : rmdirRecurse, exists, deleteme; - import std.path : dirName; - - const string tmpFolder = dirName(deleteme); - const string filepath = tmpFolder ~ "/bug15771/minas/oops/"; - const string filename = filepath ~ "output.txt"; - assert(!exists(filepath)); - - auto f = new FileLogger(filename, LogLevel.all, CreateFolder.yes); - scope(exit) () @trusted { rmdirRecurse(tmpFolder ~ "/bug15771"); }(); - - f.log("Hello World!"); - assert(exists(filepath)); - f.file.close(); -} - -@system unittest -{ - import std.array : empty; - import std.file : deleteme, remove; - import std.string : indexOf; - - string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; - auto file = File(filename, "w"); - auto l = new FileLogger(file); - - scope(exit) - { - remove(filename); - } - - string notWritten = "this should not be written to file"; - string written = "this should be written to file"; - - l.logLevel = LogLevel.critical; - l.log(LogLevel.warning, notWritten); - l.log(LogLevel.critical, written); - file.close(); - - file = File(filename, "r"); - string readLine = file.readln(); - assert(readLine.indexOf(written) != -1, readLine); - readLine = file.readln(); - assert(readLine.indexOf(notWritten) == -1, readLine); - file.close(); -} - -@system unittest -{ - auto dl = cast(FileLogger) 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; -} +public import std.logger.filelogger; diff --git a/libphobos/src/std/experimental/logger/multilogger.d b/libphobos/src/std/experimental/logger/multilogger.d index 3593690..ae00b25 100644 --- a/libphobos/src/std/experimental/logger/multilogger.d +++ b/libphobos/src/std/experimental/logger/multilogger.d @@ -1,200 +1,13 @@ -// Written in the D programming language. /** -Source: $(PHOBOSSRC std/experimental/logger/multilogger.d) -*/ + * This module is now deprecated, use $(MREF std, logger, multilogger) + * instead. + * + * Copyright: Copyright The D Language Foundation 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Source: $(PHOBOSSRC std/experimental/logger/multilogger.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ module std.experimental.logger.multilogger; - -import std.experimental.logger.core; -import std.experimental.logger.filelogger; - -/** This Element is stored inside the `MultiLogger` and associates a -`Logger` to a `string`. -*/ -struct MultiLoggerEntry -{ - string name; /// The name if the `Logger` - Logger logger; /// The stored `Logger` -} - -/** MultiLogger logs to multiple `Logger`. The `Logger`s are stored in an -`Logger[]` in their order of insertion. - -Every data logged to this `MultiLogger` will be distributed to all the $(D -Logger)s inserted into it. This `MultiLogger` implementation can -hold multiple `Logger`s with the same name. If the method `removeLogger` -is used to remove a `Logger` only the first occurrence with that name will -be removed. -*/ -class MultiLogger : Logger -{ - /** A constructor for the `MultiLogger` Logger. - - Params: - lv = The `LogLevel` for the `MultiLogger`. By default the - `LogLevel` for `MultiLogger` is `LogLevel.all`. - - Example: - ------------- - auto l1 = new MultiLogger(LogLevel.trace); - ------------- - */ - this(const LogLevel lv = LogLevel.all) @safe - { - super(lv); - } - - /** This member holds all `Logger`s stored in the `MultiLogger`. - - When inheriting from `MultiLogger` this member can be used to gain - access to the stored `Logger`. - */ - protected MultiLoggerEntry[] logger; - - /** This method inserts a new Logger into the `MultiLogger`. - - Params: - name = The name of the `Logger` to insert. - newLogger = The `Logger` to insert. - */ - void insertLogger(string name, Logger newLogger) @safe - { - this.logger ~= MultiLoggerEntry(name, newLogger); - } - - /** This method removes a Logger from the `MultiLogger`. - - Params: - toRemove = The name of the `Logger` to remove. If the `Logger` - is not found `null` will be returned. Only the first occurrence of - a `Logger` with the given name will be removed. - - Returns: The removed `Logger`. - */ - Logger removeLogger(in char[] toRemove) @safe - { - import std.algorithm.mutation : copy; - import std.range.primitives : back, popBack; - for (size_t i = 0; i < this.logger.length; ++i) - { - if (this.logger[i].name == toRemove) - { - Logger ret = this.logger[i].logger; - this.logger[i] = this.logger.back; - this.logger.popBack(); - - return ret; - } - } - - return null; - } - - /* The override to pass the payload to all children of the - `MultiLoggerBase`. - */ - override protected void writeLogMsg(ref LogEntry payload) @safe - { - foreach (it; this.logger) - { - /* We don't perform any checks here to avoid race conditions. - Instead the child will check on its own if its log level matches - and assume LogLevel.all for the globalLogLevel (since we already - know the message passes this test). - */ - it.logger.forwardMsg(payload); - } - } -} - -@safe unittest -{ - import std.exception : assertThrown; - import std.experimental.logger.nulllogger; - auto a = new MultiLogger; - auto n0 = new NullLogger(); - auto n1 = new NullLogger(); - a.insertLogger("zero", n0); - a.insertLogger("one", n1); - - auto n0_1 = a.removeLogger("zero"); - assert(n0_1 is n0); - auto n = a.removeLogger("zero"); - assert(n is null); - - auto n1_1 = a.removeLogger("one"); - assert(n1_1 is n1); - n = a.removeLogger("one"); - assert(n is null); -} - -@safe unittest -{ - auto a = new MultiLogger; - auto n0 = new TestLogger; - auto n1 = new TestLogger; - a.insertLogger("zero", n0); - a.insertLogger("one", n1); - - a.log("Hello TestLogger"); int line = __LINE__; - assert(n0.msg == "Hello TestLogger"); - assert(n0.line == line); - assert(n1.msg == "Hello TestLogger"); - assert(n1.line == line); -} - -// Issue #16 -@system unittest -{ - import std.file : deleteme; - import std.stdio : File; - import std.string : indexOf; - string logName = deleteme ~ __FUNCTION__ ~ ".log"; - auto logFileOutput = File(logName, "w"); - scope(exit) - { - import std.file : remove; - logFileOutput.close(); - remove(logName); - } - auto traceLog = new FileLogger(logFileOutput, LogLevel.all); - auto infoLog = new TestLogger(LogLevel.info); - - auto root = new MultiLogger(LogLevel.all); - root.insertLogger("fileLogger", traceLog); - root.insertLogger("stdoutLogger", infoLog); - - string tMsg = "A trace message"; - root.trace(tMsg); int line1 = __LINE__; - - assert(infoLog.line != line1); - assert(infoLog.msg != tMsg); - - string iMsg = "A info message"; - root.info(iMsg); int line2 = __LINE__; - - assert(infoLog.line == line2); - assert(infoLog.msg == iMsg, infoLog.msg ~ ":" ~ iMsg); - - logFileOutput.close(); - logFileOutput = File(logName, "r"); - assert(logFileOutput.isOpen); - assert(!logFileOutput.eof); - - auto line = logFileOutput.readln(); - assert(line.indexOf(tMsg) != -1, line ~ ":" ~ tMsg); - assert(!logFileOutput.eof); - line = logFileOutput.readln(); - assert(line.indexOf(iMsg) != -1, line ~ ":" ~ tMsg); -} - -@system unittest -{ - auto dl = cast(FileLogger) 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; -} +public import std.logger.multilogger; diff --git a/libphobos/src/std/experimental/logger/nulllogger.d b/libphobos/src/std/experimental/logger/nulllogger.d index 0ff7663..2c1f0ba 100644 --- a/libphobos/src/std/experimental/logger/nulllogger.d +++ b/libphobos/src/std/experimental/logger/nulllogger.d @@ -1,41 +1,13 @@ -// Written in the D programming language. /** -Source: $(PHOBOSSRC std/experimental/logger/nulllogger.d) -*/ + * This module is now deprecated, use $(MREF std, logger, nulllogger) + * instead. + * + * Copyright: Copyright The D Language Foundation 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Source: $(PHOBOSSRC std/experimental/logger/nulllogger.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ module std.experimental.logger.nulllogger; - -import std.experimental.logger.core; - -/** The `NullLogger` will not process any log messages. - -In case of a log message with `LogLevel.fatal` nothing will happen. -*/ -class NullLogger : Logger -{ - /** The default constructor for the `NullLogger`. - - Independent of the parameter this Logger will never log a message. - - Params: - lv = The `LogLevel` for the `NullLogger`. By default the `LogLevel` - for `NullLogger` is `LogLevel.all`. - */ - this(const LogLevel lv = LogLevel.all) @safe - { - super(lv); - this.fatalHandler = delegate() {}; - } - - override protected void writeLogMsg(ref LogEntry payload) @safe @nogc - { - } -} - -/// -@safe unittest -{ - import std.experimental.logger.core : LogLevel; - auto nl1 = new NullLogger(LogLevel.all); - nl1.info("You will never read this."); - nl1.fatal("You will never read this, either and it will not throw"); -} +public import std.logger.nulllogger; diff --git a/libphobos/src/std/experimental/logger/package.d b/libphobos/src/std/experimental/logger/package.d index 89dc713..4d19ea9 100644 --- a/libphobos/src/std/experimental/logger/package.d +++ b/libphobos/src/std/experimental/logger/package.d @@ -1,168 +1,17 @@ -// Written in the D programming language. /** -Implements logging facilities. - -Copyright: Copyright Robert "burner" Schadek 2013 -- -License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>. -Authors: $(HTTP www.svs.informatik.uni-oldenburg.de/60865.html, Robert burner Schadek) - -$(H3 Basic Logging) - -Message logging is a common approach to expose runtime information of a -program. Logging should be easy, but also flexible and powerful, therefore -`D` provides a standard interface for logging. - -The easiest way to create a log message is to write: -------------- -import std.experimental.logger; - -void main() { - log("Hello World"); -} -------------- -This will print a message to the `stderr` device. The message will contain -the filename, the line number, the name of the surrounding function, the time -and the message. - -More complex log call can go along the lines like: -------------- -log("Logging to the sharedLog with its default LogLevel"); -logf(LogLevel.info, 5 < 6, "%s to the sharedLog with its LogLevel.info", "Logging"); -info("Logging to the sharedLog with its info LogLevel"); -warning(5 < 6, "Logging to the sharedLog with its LogLevel.warning if 5 is less than 6"); -error("Logging to the sharedLog with its error LogLevel"); -errorf("Logging %s the sharedLog %s its error LogLevel", "to", "with"); -critical("Logging to the"," sharedLog with its error LogLevel"); -fatal("Logging to the sharedLog with its fatal LogLevel"); - -auto fLogger = new FileLogger("NameOfTheLogFile"); -fLogger.log("Logging to the fileLogger with its default LogLevel"); -fLogger.info("Logging to the fileLogger with its default LogLevel"); -fLogger.warning(5 < 6, "Logging to the fileLogger with its LogLevel.warning if 5 is less than 6"); -fLogger.warningf(5 < 6, "Logging to the fileLogger with its LogLevel.warning if %s is %s than 6", 5, "less"); -fLogger.critical("Logging to the fileLogger with its info LogLevel"); -fLogger.log(LogLevel.trace, 5 < 6, "Logging to the fileLogger"," with its default LogLevel if 5 is less than 6"); -fLogger.fatal("Logging to the fileLogger with its warning LogLevel"); -------------- -Additionally, this example shows how a new `FileLogger` is created. -Individual `Logger` and the global log functions share commonly named -functions to log data. - -The names of the functions are as follows: -$(UL - $(LI `log`) - $(LI `trace`) - $(LI `info`) - $(LI `warning`) - $(LI `critical`) - $(LI `fatal`) -) -The default `Logger` will by default log to `stderr` and has a default -`LogLevel` of `LogLevel.all`. The default Logger can be accessed by -using the property called `sharedLog`. This property is a reference to the -current default `Logger`. This reference can be used to assign a new -default `Logger`. -------------- -sharedLog = new FileLogger("New_Default_Log_File.log"); -------------- - -Additional `Logger` can be created by creating a new instance of the -required `Logger`. - -$(H3 Logging Fundamentals) -$(H4 LogLevel) -The `LogLevel` of a log call can be defined in two ways. The first is by -calling `log` and passing the `LogLevel` explicitly as the first argument. -The second way of setting the `LogLevel` of a -log call, is by calling either `trace`, `info`, `warning`, -`critical`, or `fatal`. The log call will then have the respective -`LogLevel`. If no `LogLevel` is defined the log call will use the -current `LogLevel` of the used `Logger`. If data is logged with -`LogLevel` `fatal` by default an `Error` will be thrown. -This behaviour can be modified by using the member `fatalHandler` to -assign a custom delegate to handle log call with `LogLevel` `fatal`. - -$(H4 Conditional Logging) -Conditional logging can be achieved be passing a `bool` as first -argument to a log function. If conditional logging is used the condition must -be `true` in order to have the log message logged. - -In order to combine an explicit `LogLevel` passing with conditional -logging, the `LogLevel` has to be passed as first argument followed by the -`bool`. - -$(H4 Filtering Log Messages) -Messages are logged if the `LogLevel` of the log message is greater than or -equal to the `LogLevel` of the used `Logger` and additionally if the -`LogLevel` of the log message is greater than or equal to the global `LogLevel`. -If a condition is passed into the log call, this condition must be true. - -The global `LogLevel` is accessible by using `globalLogLevel`. -To assign a `LogLevel` of a `Logger` use the `logLevel` property of -the logger. - -$(H4 Printf Style Logging) -If `printf`-style logging is needed add a $(B f) to the logging call, such as -$(D myLogger.infof("Hello %s", "world");) or $(D fatalf("errno %d", 1337)). -The additional $(B f) appended to the function name enables `printf`-style -logging for all combinations of explicit `LogLevel` and conditional -logging functions and methods. - -$(H4 Thread Local Redirection) -Calls to the free standing log functions are not directly forwarded to the -global `Logger` `sharedLog`. Actually, a thread local `Logger` of -type `StdForwardLogger` processes the log call and then, by default, forwards -the created `Logger.LogEntry` to the `sharedLog` `Logger`. -The thread local `Logger` is accessible by the `stdThreadLocalLog` -property. This property allows to assign user defined `Logger`. The default -`LogLevel` of the `stdThreadLocalLog` `Logger` is `LogLevel.all` -and it will therefore forward all messages to the `sharedLog` `Logger`. -The `LogLevel` of the `stdThreadLocalLog` can be used to filter log -calls before they reach the `sharedLog` `Logger`. - -$(H3 User Defined Logger) -To customize the `Logger` behavior, create a new `class` that inherits from -the abstract `Logger` `class`, and implements the `writeLogMsg` -method. -------------- -class MyCustomLogger : Logger -{ - this(LogLevel lv) @safe - { - super(lv); - } - - override void writeLogMsg(ref LogEntry payload) - { - // log message in my custom way - } -} - -auto logger = new MyCustomLogger(LogLevel.info); -logger.log("Awesome log message with LogLevel.info"); -------------- - -To gain more precise control over the logging process, additionally to -overriding the `writeLogMsg` method the methods `beginLogMsg`, -`logMsgPart` and `finishLogMsg` can be overridden. - -$(H3 Provided Logger) -By default four `Logger` implementations are given. The `FileLogger` -logs data to files. It can also be used to log to `stdout` and `stderr` -as these devices are files as well. A `Logger` that logs to `stdout` can -therefore be created by $(D new FileLogger(stdout)). -The `MultiLogger` is basically an associative array of `string`s to -`Logger`. It propagates log calls to its stored `Logger`. The -`ArrayLogger` contains an array of `Logger` and also propagates log -calls to its stored `Logger`. The `NullLogger` does not do anything. It -will never log a message and will never throw on a log call with `LogLevel` -`error`. - -Source: $(PHOBOSSRC std/experimental/logger/package.d) -*/ + * This module is now deprecated, use $(MREF std, logger) + * instead. + * + * Copyright: Copyright The D Language Foundation 2005 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: + * Source: $(PHOBOSSRC std/experimental/logger/package.d) + * + * $(SCRIPT inhibitQuickIndex = 1;) + */ module std.experimental.logger; -public import std.experimental.logger.core; -public import std.experimental.logger.filelogger; -public import std.experimental.logger.multilogger; -public import std.experimental.logger.nulllogger; +public import std.logger.core; +public import std.logger.filelogger; +public import std.logger.multilogger; +public import std.logger.nulllogger; diff --git a/libphobos/src/std/experimental/typecons.d b/libphobos/src/std/experimental/typecons.d deleted file mode 100644 index 46e21e7..0000000 --- a/libphobos/src/std/experimental/typecons.d +++ /dev/null @@ -1,1083 +0,0 @@ -// Written in the D programming language. - -/** -This module implements experimental additions/modifications to $(MREF std, typecons). - -Use this module to test out new functionality for $(REF wrap, std, typecons) -which allows for a struct to be wrapped against an interface; the -implementation in $(MREF std, typecons) only allows for classes to use the wrap -functionality. - -Source: $(PHOBOSSRC std/experimental/typecons.d) - -Copyright: Copyright the respective authors, 2008- -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Authors: $(HTTP erdani.org, Andrei Alexandrescu), - $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), - Don Clugston, - Shin Fujishiro, - Kenji Hara - */ -module std.experimental.typecons; - -import std.meta; // : AliasSeq, allSatisfy; -import std.traits; - -import std.typecons : Tuple, tuple, Bind, DerivedFunctionType, GetOverloadedMethods; - -private -{ - pragma(mangle, "_d_toObject") - extern(C) pure nothrow Object typecons_d_toObject(void* p); -} - -/* - * Avoids opCast operator overloading. - */ -private template dynamicCast(T) -if (is(T == class) || is(T == interface)) -{ - @trusted - T dynamicCast(S)(inout S source) - if (is(S == class) || is(S == interface)) - { - static if (is(Unqual!S : Unqual!T)) - { - import std.traits : QualifierOf; - alias Qual = QualifierOf!S; // SharedOf or MutableOf - alias TmpT = Qual!(Unqual!T); - inout(TmpT) tmp = source; // bypass opCast by implicit conversion - return *cast(T*)(&tmp); // + variable pointer cast + dereference - } - else - { - return cast(T) typecons_d_toObject(*cast(void**)(&source)); - } - } -} - -@system unittest -{ - class C { @disable void opCast(T)(); } - auto c = new C; - static assert(!__traits(compiles, cast(Object) c)); - auto o = dynamicCast!Object(c); - assert(c is o); - - interface I { @disable void opCast(T)(); Object instance(); } - interface J { @disable void opCast(T)(); Object instance(); } - class D : I, J { Object instance() { return this; } } - I i = new D(); - static assert(!__traits(compiles, cast(J) i)); - J j = dynamicCast!J(i); - assert(i.instance() is j.instance()); -} - -/* - * Determines if the `Source` type satisfies all interface requirements of - * `Targets`. - */ -private template implementsInterface(Source, Targets...) -if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) -{ - import std.meta : staticMap; - - // strict upcast - bool implementsInterface()() - if (Targets.length == 1 && is(Source : Targets[0])) - { - return true; - } - // structural upcast - template implementsInterface() - if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets)) - { - auto implementsInterface() - { - return hasRequiredMethods!(); - } - - // list of FuncInfo - alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!Targets); - // list of function symbols - alias SourceMembers = GetOverloadedMethods!Source; - - // Check whether all of SourceMembers satisfy covariance target in - // TargetMembers - template hasRequiredMethods(size_t i = 0) - { - static if (i >= TargetMembers.length) - enum hasRequiredMethods = true; - else - { - enum foundFunc = findCovariantFunction!(TargetMembers[i], Source, SourceMembers); - - version (StdUnittest) {} - else debug - { - static if (foundFunc == -1) - pragma(msg, "Could not locate matching function for: ", - TargetMembers[i].stringof); - } - - enum hasRequiredMethods = - foundFunc != -1 && - hasRequiredMethods!(i + 1); - } - } - } -} -// ditto -private template implementsInterface(Source, Targets...) -if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets)) -{ - import std.meta : staticMap; - - alias implementsInterface = .implementsInterface!(Source, staticMap!(Unqual, Targets)); -} - -@safe unittest -{ - interface Foo { - void foo(); - } - interface Bar { - void bar(); - } - interface FooBar : Foo, Bar { - void foobar(); - } - - struct A { - void foo() {} - } - struct B { - void bar() {} - void foobar() {} - } - class C { - void foo() {} - void bar() {} - } - struct D { - void foo() {} - void bar() {} - void foobar() {} - } - // Implements interface - static assert(implementsInterface!(A, Foo)); - static assert(implementsInterface!(A, const(Foo))); - static assert(implementsInterface!(A, immutable(Foo))); - // Doesn't implement interface - static assert(!implementsInterface!(B, Foo)); - static assert(implementsInterface!(B, Bar)); - // Implements both interfaces - static assert(implementsInterface!(C, Foo)); - static assert(implementsInterface!(C, Bar)); - static assert(implementsInterface!(C, Foo, Bar)); - static assert(implementsInterface!(C, Foo, const(Bar))); - static assert(!implementsInterface!(A, Foo, Bar)); - static assert(!implementsInterface!(A, Foo, immutable(Bar))); - // Implements inherited - static assert(implementsInterface!(D, FooBar)); - static assert(!implementsInterface!(B, FooBar)); -} - -private enum isInterface(ConceptType) = is(ConceptType == interface); - -/// -template wrap(Targets...) -if (Targets.length >= 1 && allSatisfy!(isInterface, Targets)) -{ - import std.meta : ApplyLeft, staticMap; - - version (StdDdoc) - { - /** - * Wrap src in an anonymous class implementing $(D_PARAM Targets). - * - * wrap creates an internal wrapper class which implements the - * interfaces in `Targets` using the methods of `src`, then returns a - * GC-allocated instance of it. - * - * $(D_PARAM Source) can be either a `class` or a `struct`, but it must - * $(I structurally conform) with all the $(D_PARAM Targets) - * interfaces; i.e. it must provide concrete methods with compatible - * signatures of those in $(D_PARAM Targets). - * - * If $(D_PARAM Source) is a `struct` then wrapping/unwrapping will - * create a copy; it is not possible to affect the original `struct` - * through the wrapper. - * - * The returned object additionally supports $(LREF unwrap). - * - * Note: - * If $(D_PARAM Targets) has only one entry and $(D_PARAM Source) is a - * class which explicitly implements it, wrap simply returns src - * upcasted to `Targets[0]`. - * - * Bugs: - * wrap does not support interfaces which take their own type as either - * a parameter type or return type in any of its methods. - * - * See_Also: $(LREF unwrap) for examples - */ - auto wrap(Source)(inout Source src) - if (implementsInterface!(Source, Targets)); - } - - static if (!allSatisfy!(isMutable, Targets)) - alias wrap = .wrap!(staticMap!(Unqual, Targets)); - else - { - // strict upcast - auto wrap(Source)(inout Source src) - if (Targets.length == 1 && is(Source : Targets[0])) - { - alias T = Select!(is(Source == shared), shared Targets[0], Targets[0]); - return dynamicCast!(inout T)(src); - } - - // structural upcast - template wrap(Source) - if (!allSatisfy!(ApplyLeft!(isImplicitlyConvertible, Source), Targets)) - { - auto wrap(inout Source src) - { - static assert(implementsInterface!(Source, Targets), - "Source "~Source.stringof~ - " does not have structural conformance to "~ - Targets.stringof); - - alias T = Select!(is(Source == shared), shared Impl, Impl); - return new inout T(src); - } - - // list of FuncInfo - alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!(Targets)); - // list of function symbols - alias SourceMembers = GetOverloadedMethods!Source; - - static if (is(Source == class) || is(Source == interface)) - alias StructuralType = Object; - else static if (is(Source == struct)) - alias StructuralType = Source; - - // Check whether all of SourceMembers satisfy covariance target in TargetMembers - // Internal wrapper class - final class Impl : Structural!StructuralType, Targets - { - private: - Source _wrap_source; - - this( inout Source s) inout @safe pure nothrow { _wrap_source = s; } - this(shared inout Source s) shared inout @safe pure nothrow { _wrap_source = s; } - - static if (is(Source == class) || is(Source == interface)) - { - // BUG: making private should work with NVI. - protected inout(Object) _wrap_getSource() inout @safe - { - return dynamicCast!(inout Object)(_wrap_source); - } - } - else - { - // BUG: making private should work with NVI. - protected inout(Source) _wrap_getSource() inout @safe - { - return _wrap_source; - } - } - - import std.conv : to; - import core.lifetime : forward; - template generateFun(size_t i) - { - enum name = TargetMembers[i].name; - enum fa = functionAttributes!(TargetMembers[i].type); - static args(int num)() - { - string r; - bool first = true; - foreach (i; 0 .. num) - { - import std.conv : to; - r ~= (first ? "" : ", ") ~ " a" ~ (i+1).to!string; - first = false; - } - return r; - } - static if (fa & FunctionAttribute.property) - { - static if (Parameters!(TargetMembers[i].type).length == 0) - enum fbody = "_wrap_source."~name; - else - enum fbody = "_wrap_source."~name~" = a1"; - } - else - { - enum fbody = "_wrap_source."~name~"("~args!(Parameters!(TargetMembers[i].type).length)~")"; - } - enum generateFun = - "override "~wrapperSignature!(TargetMembers[i]) ~ - "{ return "~fbody~"; }"; - } - - public: - static foreach (i; 0 .. TargetMembers.length) - mixin(generateFun!i); - } - } - } -} - -// Build a signature that matches the provided function -// Each argument will be provided a name in the form a# -private template wrapperSignature(alias fun) -{ - enum name = fun.name; - enum fa = functionAttributes!(fun.type); - static @property stc() - { - string r; - if (fa & FunctionAttribute.property) r ~= "@property "; - if (fa & FunctionAttribute.ref_) r ~= "ref "; - if (fa & FunctionAttribute.pure_) r ~= "pure "; - if (fa & FunctionAttribute.nothrow_) r ~= "nothrow "; - if (fa & FunctionAttribute.trusted) r ~= "@trusted "; - if (fa & FunctionAttribute.safe) r ~= "@safe "; - return r; - } - static @property mod() - { - alias type = AliasSeq!(fun.type)[0]; - string r; - static if (is(type == immutable)) r ~= " immutable"; - else - { - static if (is(type == shared)) r ~= " shared"; - static if (is(type == const)) r ~= " const"; - else static if (is(type == inout)) r ~= " inout"; - //else --> mutable - } - return r; - } - alias param = Parameters!(fun.type); - static @property wrapperParameters() - { - string r; - bool first = true; - foreach (i, p; param) - { - import std.conv : to; - r ~= (first ? "" : ", ") ~ p.stringof ~ " a" ~ (i+1).to!string; - first = false; - } - return r; - } - - enum wrapperSignature = - stc~ReturnType!(fun.type).stringof ~ " " - ~ name~"("~wrapperParameters~")"~mod; -} - -@safe unittest -{ - interface M - { - void f1(); - void f2(string[] args, int count); - void f3(string[] args, int count) pure const; - } - - alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!M); - static assert(wrapperSignature!(TargetMembers[0]) == "void f1()" - , wrapperSignature!(TargetMembers[0])); - - static assert(wrapperSignature!(TargetMembers[1]) == "void f2(string[] a1, int a2)" - , wrapperSignature!(TargetMembers[1])); - - static assert(wrapperSignature!(TargetMembers[2]) == "pure void f3(string[] a1, int a2) const" - , wrapperSignature!(TargetMembers[2])); -} - -// Internal class to support dynamic cross-casting -private interface Structural(T) -{ - inout(T) _wrap_getSource() inout @safe pure nothrow; -} - -private string unwrapExceptionText(Source, Target)() -{ - return Target.stringof~ " not wrapped into "~ Source.stringof; -} - -version (StdDdoc) -{ - /** - * Extract object previously wrapped by $(LREF wrap). - * - * Params: - * Target = type of wrapped object - * src = wrapper object returned by $(LREF wrap) - * - * Returns: the wrapped object, or null if src is not a wrapper created - * by $(LREF wrap) and $(D_PARAM Target) is a class - * - * Throws: $(REF ConvException, std, conv) when attempting to extract a - * struct which is not the wrapped type - * - * See_also: $(LREF wrap) - */ - public inout(Target) unwrap(Target, Source)(inout Source src); -} - -/// -@system unittest -{ - interface Quack - { - int quack(); - @property int height(); - } - interface Flyer - { - @property int height(); - } - class Duck : Quack - { - int quack() { return 1; } - @property int height() { return 10; } - } - class Human - { - int quack() { return 2; } - @property int height() { return 20; } - } - struct HumanStructure - { - int quack() { return 3; } - @property int height() { return 30; } - } - - Duck d1 = new Duck(); - Human h1 = new Human(); - HumanStructure hs1; - - interface Refreshable - { - int refresh(); - } - // does not have structural conformance - static assert(!__traits(compiles, d1.wrap!Refreshable)); - static assert(!__traits(compiles, h1.wrap!Refreshable)); - static assert(!__traits(compiles, hs1.wrap!Refreshable)); - - // strict upcast - Quack qd = d1.wrap!Quack; - assert(qd is d1); - assert(qd.quack() == 1); // calls Duck.quack - // strict downcast - Duck d2 = qd.unwrap!Duck; - assert(d2 is d1); - - // structural upcast - Quack qh = h1.wrap!Quack; - Quack qhs = hs1.wrap!Quack; - assert(qh.quack() == 2); // calls Human.quack - assert(qhs.quack() == 3); // calls HumanStructure.quack - // structural downcast - Human h2 = qh.unwrap!Human; - HumanStructure hs2 = qhs.unwrap!HumanStructure; - assert(h2 is h1); - assert(hs2 is hs1); - - // structural upcast (two steps) - Quack qx = h1.wrap!Quack; // Human -> Quack - Quack qxs = hs1.wrap!Quack; // HumanStructure -> Quack - Flyer fx = qx.wrap!Flyer; // Quack -> Flyer - Flyer fxs = qxs.wrap!Flyer; // Quack -> Flyer - assert(fx.height == 20); // calls Human.height - assert(fxs.height == 30); // calls HumanStructure.height - // strucural downcast (two steps) - Quack qy = fx.unwrap!Quack; // Flyer -> Quack - Quack qys = fxs.unwrap!Quack; // Flyer -> Quack - Human hy = qy.unwrap!Human; // Quack -> Human - HumanStructure hys = qys.unwrap!HumanStructure; // Quack -> HumanStructure - assert(hy is h1); - assert(hys is hs1); - // strucural downcast (one step) - Human hz = fx.unwrap!Human; // Flyer -> Human - HumanStructure hzs = fxs.unwrap!HumanStructure; // Flyer -> HumanStructure - assert(hz is h1); - assert(hzs is hs1); -} - -/// -@system unittest -{ - import std.traits : functionAttributes, FunctionAttribute; - interface A { int run(); } - interface B { int stop(); @property int status(); } - class X - { - int run() { return 1; } - int stop() { return 2; } - @property int status() { return 3; } - } - - auto x = new X(); - auto ab = x.wrap!(A, B); - A a = ab; - B b = ab; - assert(a.run() == 1); - assert(b.stop() == 2); - assert(b.status == 3); - static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property); -} - -template unwrap(Target) -{ - static if (!isMutable!Target) - alias unwrap = .unwrap!(Unqual!Target); - else - { - // strict downcast - auto unwrap(Source)(inout Source src) - if (is(Target : Source)) - { - alias T = Select!(is(Source == shared), shared Target, Target); - return dynamicCast!(inout T)(src); - } - - // structural downcast for struct target - auto unwrap(Source)(inout Source src) - if (is(Target == struct)) - { - alias T = Select!(is(Source == shared), shared Target, Target); - auto upCastSource = dynamicCast!Object(src); // remove qualifier - do - { - if (auto a = dynamicCast!(Structural!Object)(upCastSource)) - { - upCastSource = a._wrap_getSource(); - } - else if (auto a = dynamicCast!(Structural!T)(upCastSource)) - { - return a._wrap_getSource(); - } - else - { - static if (hasMember!(Source, "_wrap_getSource")) - return unwrap!Target(src._wrap_getSource()); - else - break; - } - } while (upCastSource); - import std.conv : ConvException; - throw new ConvException(unwrapExceptionText!(Source,Target)); - } - // structural downcast for class target - auto unwrap(Source)(inout Source src) - if (!is(Target : Source) && !is(Target == struct)) - { - alias T = Select!(is(Source == shared), shared Target, Target); - Object upCastSource = dynamicCast!(Object)(src); // remove qualifier - do - { - // Unwrap classes - if (auto a = dynamicCast!(Structural!Object)(upCastSource)) - { - if (auto d = dynamicCast!(inout T)(upCastSource = a._wrap_getSource())) - return d; - } - // Unwrap a structure of type T - else if (auto a = dynamicCast!(Structural!T)(upCastSource)) - { - return a._wrap_getSource(); - } - // Unwrap class that already inherited from interface - else if (auto d = dynamicCast!(inout T)(upCastSource)) - { - return d; - } - // Recurse to find the struct Target within a wrapped tree - else - { - static if (hasMember!(Source, "_wrap_getSource")) - return unwrap!Target(src._wrap_getSource()); - else - break; - } - } while (upCastSource); - return null; - } - } -} - -@system unittest -{ - // Validate const/immutable - class A - { - int draw() { return 1; } - int draw(int v) { return v; } - - int draw() const { return 2; } - int draw() shared { return 3; } - int draw() shared const { return 4; } - int draw() immutable { return 5; } - } - interface Drawable - { - int draw(); - int draw() const; - int draw() shared; - int draw() shared const; - int draw() immutable; - } - interface Drawable2 - { - int draw(int v); - } - - auto ma = new A(); - auto sa = new shared A(); - auto ia = new immutable A(); - { - Drawable md = ma.wrap!Drawable; - const Drawable cd = ma.wrap!Drawable; - shared Drawable sd = sa.wrap!Drawable; - shared const Drawable scd = sa.wrap!Drawable; - immutable Drawable id = ia.wrap!Drawable; - assert( md.draw() == 1); - assert( cd.draw() == 2); - assert( sd.draw() == 3); - assert(scd.draw() == 4); - assert( id.draw() == 5); - } - { - Drawable2 d = ma.wrap!Drawable2; - static assert(!__traits(compiles, d.draw())); - assert(d.draw(10) == 10); - } -} - -// https://issues.dlang.org/show_bug.cgi?id=10377 -@system unittest -{ - import std.algorithm, std.range; - - interface MyInputRange(T) - { - @property T front(); - void popFront(); - @property bool empty(); - } - - //auto o = iota(0,10,1).inputRangeObject(); - //pragma(msg, __traits(allMembers, typeof(o))); - auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)(); - assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); -} - -// https://issues.dlang.org/show_bug.cgi?id=10536 -@system unittest -{ - interface Interface - { - int foo(); - } - class Pluggable - { - int foo() { return 1; } - @disable void opCast(T, this X)(); // ! - } - - Interface i = new Pluggable().wrap!Interface; - assert(i.foo() == 1); -} - -// https://issues.dlang.org/show_bug.cgi?id=10538 -@system unittest -{ - interface Interface - { - int foo(); - int bar(int); - } - class Pluggable - { - int opDispatch(string name, A...)(A args) { return 100; } - } - - Interface i = wrap!Interface(new Pluggable()); - assert(i.foo() == 100); - assert(i.bar(10) == 100); -} - -// Concat all Targets function members into one tuple -private template ConcatInterfaceMembers(Targets...) -{ - static if (Targets.length == 0) - alias ConcatInterfaceMembers = AliasSeq!(); - else static if (Targets.length == 1) - alias ConcatInterfaceMembers - = AliasSeq!(GetOverloadedMethods!(Targets[0])); - else - alias ConcatInterfaceMembers = AliasSeq!( - GetOverloadedMethods!(Targets[0]), - ConcatInterfaceMembers!(Targets[1..$])); -} -// Remove duplicated functions based on the identifier name and function type covariance -private template UniqMembers(members...) -{ - template FuncInfo(string s, F) - { - enum name = s; - alias type = F; - } - - static if (members.length == 0) - alias UniqMembers = AliasSeq!(); - else - { - alias func = members[0]; - enum name = __traits(identifier, func); - alias type = FunctionTypeOf!func; - template check(size_t i, mem...) - { - static if (i >= mem.length) - enum ptrdiff_t check = -1; - else static if - (__traits(identifier, func) == __traits(identifier, mem[i]) && - !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void)) - { - enum ptrdiff_t check = i; - } - else - enum ptrdiff_t check = check!(i + 1, mem); - } - enum ptrdiff_t x = 1 + check!(0, members[1 .. $]); - static if (x >= 1) - { - alias typex = DerivedFunctionType!(type, FunctionTypeOf!(members[x])); - alias remain = UniqMembers!(members[1 .. x], members[x + 1 .. $]); - - static if (remain.length >= 1 && remain[0].name == name && - !is(DerivedFunctionType!(typex, remain[0].type) == void)) - { - alias F = DerivedFunctionType!(typex, remain[0].type); - alias UniqMembers = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]); - } - else - alias UniqMembers = AliasSeq!(FuncInfo!(name, typex), remain); - } - else - { - alias UniqMembers = AliasSeq!(FuncInfo!(name, type), UniqMembers!(members[1 .. $])); - } - } -} - -// find a function from Fs that has same identifier and covariant type with f -private template findCovariantFunction(alias finfo, Source, Fs...) -{ - template check(size_t i = 0) - { - static if (i >= Fs.length) - enum ptrdiff_t check = -1; - else - { - enum ptrdiff_t check = - (finfo.name == __traits(identifier, Fs[i])) && - isCovariantWith!(FunctionTypeOf!(Fs[i]), finfo.type) - ? i : check!(i + 1); - } - } - enum x = check!(); - static if (x == -1 && is(typeof(Source.opDispatch))) - { - alias Params = Parameters!(finfo.type); - enum ptrdiff_t findCovariantFunction = - is(typeof(( Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof(( const Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof(( immutable Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof(( shared Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof((shared const Source).init.opDispatch!(finfo.name)(Params.init))) - ? ptrdiff_t.max : -1; - } - else - enum ptrdiff_t findCovariantFunction = x; -} - -/** -Type constructor for final (aka head-const) variables. - -Final variables cannot be directly mutated or rebound, but references -reached through the variable are typed with their original mutability. -It is equivalent to `final` variables in D1 and Java, as well as -`readonly` variables in C#. - -When `T` is a `const` or `immutable` type, `Final` aliases -to `T`. -*/ -template Final(T) -{ -static if (is(T == const) || is(T == immutable)) - alias Final = T; -else -{ - struct Final - { - import std.typecons : Proxy; - - private T final_value; - mixin Proxy!final_value; - - /** - * Construction is forwarded to the underlying type. - */ - this(T other) - { - this.final_value = other; - } - - /// Ditto - this(Args...)(auto ref Args args) - if (__traits(compiles, T(args))) - { - static assert((!is(T == struct) && !is(T == union)) || !isNested!T, - "Non-static nested type " ~ fullyQualifiedName!T ~ " must be " ~ - "constructed explicitly at the call-site (e.g. auto s = " ~ - "makeFinal(" ~ T.stringof ~ "(...));)"); - this.final_value = T(args); - } - - // Attaching function attributes gives less noisy error messages - pure nothrow @safe @nogc - { - /++ - + All operators, including member access, are forwarded to the - + underlying value of type `T` except for these mutating operators, - + which are disabled. - +/ - void opAssign(Other)(Other other) - { - static assert(0, typeof(this).stringof ~ - " cannot be reassigned."); - } - - /// Ditto - void opOpAssign(string op, Other)(Other other) - { - static assert(0, typeof(this).stringof ~ - " cannot be reassigned."); - } - - /// Ditto - void opUnary(string op : "--")() - { - static assert(0, typeof(this).stringof ~ - " cannot be mutated."); - } - - /// Ditto - void opUnary(string op : "++")() - { - static assert(0, typeof(this).stringof ~ - " cannot be mutated."); - } - } - - /** - * - * `Final!T` implicitly converts to an rvalue of type `T` through - * `AliasThis`. - */ - inout(T) final_get() inout - { - return final_value; - } - - /// Ditto - alias final_get this; - - /// Ditto - auto ref opUnary(string op)() - if (__traits(compiles, mixin(op ~ "T.init"))) - { - return mixin(op ~ "this.final_value"); - } - } -} -} - -/// Ditto -Final!T makeFinal(T)(T t) -{ - return Final!T(t); -} - -/// `Final` can be used to create class references which cannot be rebound: -pure nothrow @safe unittest -{ - static class A - { - int i; - - this(int i) pure nothrow @nogc @safe - { - this.i = i; - } - } - - auto a = makeFinal(new A(42)); - assert(a.i == 42); - - //a = new A(24); // Reassignment is illegal, - a.i = 24; // But fields are still mutable. - - assert(a.i == 24); -} - -/// `Final` can also be used to create read-only data fields without using transitive immutability: -pure nothrow @safe unittest -{ - static class A - { - int i; - - this(int i) pure nothrow @nogc @safe - { - this.i = i; - } - } - - static class B - { - Final!A a; - - this(A a) pure nothrow @nogc @safe - { - this.a = a; // Construction, thus allowed. - } - } - - auto b = new B(new A(42)); - assert(b.a.i == 42); - - // b.a = new A(24); // Reassignment is illegal, - b.a.i = 24; // but `a` is still mutable. - - assert(b.a.i == 24); -} - -pure nothrow @safe unittest -{ - static class A { int i; } - static assert(!is(Final!A == A)); - static assert(is(Final!(const A) == const A)); - static assert(is(Final!(immutable A) == immutable A)); - - Final!A a = new A; - static assert(!__traits(compiles, a = new A)); - - assert(a.i == 0); - a.i = 42; - assert(a.i == 42); - - Final!int i = 42; - static assert(!__traits(compiles, i = 24)); - static assert(!__traits(compiles, --i)); - static assert(!__traits(compiles, ++i)); - assert(i == 42); - int iCopy = i; - assert(iCopy == 42); - iCopy = -i; // non-mutating unary operators must work - assert(iCopy == -42); - - static struct S - { - int i; - - pure nothrow @safe @nogc: - this(int i){} - this(string s){} - this(int i, string s, float f){ this.i = i; } - } - - Final!S sint = 42; - Final!S sstr = "foo"; - static assert(!__traits(compiles, sint = sstr)); - - auto sboth = Final!S(42, "foo", 3.14); - assert(sboth.i == 42); - - sboth.i = 24; - assert(sboth.i == 24); - - struct NestedS - { - int i; - int get() pure nothrow @safe @nogc { return sboth.i + i; } - } - - // Nested structs must be constructed at the call-site - static assert(!__traits(compiles, Final!NestedS(6))); - auto s = makeFinal(NestedS(6)); - assert(s.i == 6); - assert(s.get == 30); - - class NestedC - { - int i; - - pure nothrow @safe @nogc: - this(int i) { this.i = i; } - int get() { return sboth.i + i; } - } - - auto c = makeFinal(new NestedC(6)); - assert(c.i == 6); - assert(c.get == 30); -} - -pure nothrow @safe unittest -{ - auto arr = makeFinal([1, 2, 3]); - static assert(!__traits(compiles, arr = null)); - static assert(!__traits(compiles, arr ~= 4)); - assert((arr ~ 4) == [1, 2, 3, 4]); -} - -// https://issues.dlang.org/show_bug.cgi?id=17270 -pure nothrow @nogc @system unittest -{ - int i = 1; - Final!(int*) fp = &i; - assert(*fp == 1); - static assert(!__traits(compiles, - fp = &i // direct assignment - )); - static assert(is(typeof(*fp) == int)); - *fp = 2; // indirect assignment - assert(*fp == 2); - int* p = fp; - assert(*p == 2); -} - -pure nothrow @system unittest -{ - Final!(int[]) arr; - // static assert(!__traits(compiles, - // arr.length = 10; // bug! - // )); - static assert(!__traits(compiles, - arr.ptr = null - )); - static assert(!__traits(compiles, - arr.ptr++ - )); -} diff --git a/libphobos/src/std/getopt.d b/libphobos/src/std/getopt.d index c1c5cd2..b239901 100644 --- a/libphobos/src/std/getopt.d +++ b/libphobos/src/std/getopt.d @@ -1835,7 +1835,7 @@ void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, st assert(flag); } -@safe unittest // Delegates as callbacks +@system unittest // Delegates as callbacks { alias TwoArgOptionHandler = void delegate(string option, string value) @safe; diff --git a/libphobos/src/std/json.d b/libphobos/src/std/json.d index c6e746a..ac397e5 100644 --- a/libphobos/src/std/json.d +++ b/libphobos/src/std/json.d @@ -1537,19 +1537,15 @@ if (isOutputRange!(Out,char)) toStringImpl!char(str); } - // recursive @safe inference is broken here - // workaround: if json.put is @safe, we should be too, - // so annotate the recursion as @safe manually - static if (isSafe!({ json.put(""); })) - { - void delegate(ref const JSONValue, ulong) @safe toValue; - } - else - { - void delegate(ref const JSONValue, ulong) @system toValue; - } + /* make the function infer @system when json.put() is @system + */ + if (0) + json.put(' '); - void toValueImpl(ref const JSONValue value, ulong indentLevel) + /* Mark as @trusted because json.put() may be @system. This has difficulty + * inferring @safe because it is recursive. + */ + void toValueImpl(ref const JSONValue value, ulong indentLevel) @trusted { void putTabs(ulong additionalIndent = 0) { @@ -1594,7 +1590,7 @@ if (isOutputRange!(Out,char)) json.put(':'); if (pretty) json.put(' '); - toValue(member, indentLevel + 1); + toValueImpl(member, indentLevel + 1); } } @@ -1631,7 +1627,7 @@ if (isOutputRange!(Out,char)) if (i) putCharAndEOL(','); putTabs(1); - toValue(el, indentLevel + 1); + toValueImpl(el, indentLevel + 1); } putEOL(); putTabs(); @@ -1710,9 +1706,7 @@ if (isOutputRange!(Out,char)) } } - toValue = &toValueImpl; - - toValue(root, 0); + toValueImpl(root, 0); } // https://issues.dlang.org/show_bug.cgi?id=12897 diff --git a/libphobos/src/std/logger/core.d b/libphobos/src/std/logger/core.d new file mode 100644 index 0000000..be2bd8d --- /dev/null +++ b/libphobos/src/std/logger/core.d @@ -0,0 +1,3049 @@ +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/logger/core.d) +*/ +module std.logger.core; + +import core.atomic : atomicLoad, atomicOp, atomicStore, MemoryOrder; +import core.sync.mutex : Mutex; +import std.datetime.date : DateTime; +import std.datetime.systime : Clock, SysTime; +import std.range.primitives; +import std.traits; + +import std.logger.filelogger; + +/** This functions is used at runtime to determine if a `LogLevel` is +active. The same previously defined version statements are used to disable +certain levels. Again the version statements are associated with a compile +unit and can therefore not disable logging in other compile units. +pure bool isLoggingEnabled()(LogLevel ll) @safe nothrow @nogc +*/ +bool isLoggingEnabled()(LogLevel ll, LogLevel loggerLL, + LogLevel globalLL, lazy bool condition = true) @safe +{ + return ll >= globalLL + && ll >= loggerLL + && ll != LogLevel.off + && globalLL != LogLevel.off + && loggerLL != LogLevel.off + && condition; +} + +/* This function formates a `SysTime` into an `OutputRange`. + +The `SysTime` is formatted similar to +$(LREF std.datatime.DateTime.toISOExtString) except the fractional second part. +The fractional second part is in milliseconds and is always 3 digits. +*/ +void systimeToISOString(OutputRange)(OutputRange o, const ref SysTime time) +if (isOutputRange!(OutputRange,string)) +{ + import std.format.write : formattedWrite; + + const auto dt = cast(DateTime) time; + const auto fsec = time.fracSecs.total!"msecs"; + + formattedWrite(o, "%04d-%02d-%02dT%02d:%02d:%02d.%03d", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + fsec); +} + +/** This function logs data. + +In order for the data to be processed, the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog` and the +`defaultLogLevel`; additionally the condition passed must be `true`. + +Params: + ll = The `LogLevel` used by this log call. + condition = The condition must be `true` for the data to be logged. + args = The data that should be logged. + +Example: +-------------------- +log(LogLevel.warning, true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) +if (args.length != 1) +{ + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, args); +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__) +{ + stdThreadLocalLog.log!(T,moduleName)(ll, condition, arg, line, file, funcName, + prettyFuncName); +} + +/** This function logs data. + +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog`. + +Params: + ll = The `LogLevel` used by this log call. + args = The data that should be logged. + +Example: +-------------------- +log(LogLevel.warning, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) +if (args.length > 1 && !is(Unqual!(A[0]) : bool)) +{ + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (ll, args); +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(const LogLevel ll, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + stdThreadLocalLog.log!T(ll, arg, line, file, funcName, prettyFuncName, + moduleName); +} + +/** This function logs data. + +In order for the data to be processed the `LogLevel` of the +`sharedLog` must be greater or equal to the `defaultLogLevel` +add the condition passed must be `true`. + +Params: + condition = The condition must be `true` for the data to be logged. + args = The data that should be logged. + +Example: +-------------------- +log(true, "Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) +if (args.length != 1) +{ + stdThreadLocalLog.log!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, args); +} + +/// Ditto +void log(T, string moduleName = __MODULE__)(lazy bool condition, lazy T arg, + int line = __LINE__, string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) +{ + stdThreadLocalLog.log!(T,moduleName)(stdThreadLocalLog.logLevel, + condition, arg, line, file, funcName, prettyFuncName); +} + +/** This function logs data. + +In order for the data to be processed the `LogLevel` of the +`sharedLog` must be greater or equal to the `defaultLogLevel`. + +Params: + args = The data that should be logged. + +Example: +-------------------- +log("Hello World", 3.1415); +-------------------- +*/ +void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) +if ((args.length > 1 && !is(Unqual!(A[0]) : bool) + && !is(Unqual!(A[0]) == LogLevel)) + || args.length == 0) +{ + stdThreadLocalLog.log!(line, file, funcName, + prettyFuncName, moduleName)(stdThreadLocalLog.logLevel, args); +} + +void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) +{ + stdThreadLocalLog.log!T(stdThreadLocalLog.logLevel, arg, line, file, + funcName, prettyFuncName, moduleName); +} + +/** This function logs data in a `printf`-style manner. + +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog` and the +`defaultLogLevel` additionally the condition passed must be `true`. + +Params: + ll = The `LogLevel` used by this log call. + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. + args = The data that should be logged. + +Example: +-------------------- +logf(LogLevel.warning, true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) +{ + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, condition, msg, args); +} + +/** This function logs data in a `printf`-style manner. + +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `LogLevel` of the `sharedLog` and the +`defaultLogLevel`. + +Params: + ll = The `LogLevel` used by this log call. + msg = The `printf`-style string. + args = The data that should be logged. + +Example: +-------------------- +logf(LogLevel.warning, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy string msg, + lazy A args) +{ + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (ll, msg, args); +} + +/** This function logs data in a `printf`-style manner. + +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `defaultLogLevel` additionally the condition +passed must be `true`. + +Params: + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. + args = The data that should be logged. + +Example: +-------------------- +logf(true, "Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) +{ + stdThreadLocalLog.logf!(line, file, funcName, prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, condition, msg, args); +} + +/** This function logs data in a `printf`-style manner. + +In order for the data to be processed the `LogLevel` of the log call must +be greater or equal to the `defaultLogLevel`. + +Params: + msg = The `printf`-style string. + args = The data that should be logged. + +Example: +-------------------- +logf("Hello World %f", 3.1415); +-------------------- +*/ +void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) +{ + stdThreadLocalLog.logf!(line, file, funcName,prettyFuncName, moduleName) + (stdThreadLocalLog.logLevel, msg, args); +} + +/** This template provides the global log functions with the `LogLevel` +is encoded in the function name. + +The aliases following this template create the public names of these log +functions. +*/ +template defaultLogFunction(LogLevel ll) +{ + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if ((args.length > 0 && !is(Unqual!(A[0]) : bool)) || args.length == 0) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(args); + } + + void defaultLogFunction(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + { + stdThreadLocalLog.memLogFunctions!(ll).logImpl!(line, file, funcName, + prettyFuncName, moduleName)(condition, args); + } +} + +/** This function logs data to the `stdThreadLocalLog`, optionally depending +on a condition. + +In order for the resulting log message to be logged the `LogLevel` must +be greater or equal than the `LogLevel` of the `stdThreadLocalLog` and +must be greater or equal than the global `LogLevel`. +Additionally the `LogLevel` must be greater or equal than the `LogLevel` +of the `stdSharedLogger`. +If a condition is given, it must evaluate to `true`. + +Params: + condition = The condition must be `true` for the data to be logged. + args = The data that should be logged. + +Example: +-------------------- +trace(1337, "is number"); +info(1337, "is number"); +error(1337, "is number"); +critical(1337, "is number"); +fatal(1337, "is number"); +trace(true, 1337, "is number"); +info(false, 1337, "is number"); +error(true, 1337, "is number"); +critical(false, 1337, "is number"); +fatal(true, 1337, "is number"); +-------------------- +*/ +alias trace = defaultLogFunction!(LogLevel.trace); +/// Ditto +alias info = defaultLogFunction!(LogLevel.info); +/// Ditto +alias warning = defaultLogFunction!(LogLevel.warning); +/// Ditto +alias error = defaultLogFunction!(LogLevel.error); +/// Ditto +alias critical = defaultLogFunction!(LogLevel.critical); +/// Ditto +alias fatal = defaultLogFunction!(LogLevel.fatal); + +/** This template provides the global `printf`-style log functions with +the `LogLevel` is encoded in the function name. + +The aliases following this template create the public names of the log +functions. +*/ +template defaultLogFunctionf(LogLevel ll) +{ + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(msg, args); + } + + void defaultLogFunctionf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) + { + stdThreadLocalLog.memLogFunctions!(ll).logImplf!(line, file, funcName, + prettyFuncName, moduleName)(condition, msg, args); + } +} + +/** This function logs data to the `sharedLog` in a `printf`-style +manner. + +In order for the resulting log message to be logged the `LogLevel` must +be greater or equal than the `LogLevel` of the `sharedLog` and +must be greater or equal than the global `LogLevel`. +Additionally the `LogLevel` must be greater or equal than the `LogLevel` +of the `stdSharedLogger`. + +Params: + msg = The `printf`-style string. + args = The data that should be logged. + +Example: +-------------------- +tracef("is number %d", 1); +infof("is number %d", 2); +errorf("is number %d", 3); +criticalf("is number %d", 4); +fatalf("is number %d", 5); +-------------------- + +The second version of the function logs data to the `sharedLog` in a $(D +printf)-style manner. + +In order for the resulting log message to be logged the `LogLevel` must +be greater or equal than the `LogLevel` of the `sharedLog` and +must be greater or equal than the global `LogLevel`. +Additionally the `LogLevel` must be greater or equal than the `LogLevel` +of the `stdSharedLogger`. + +Params: + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. + args = The data that should be logged. + +Example: +-------------------- +tracef(false, "is number %d", 1); +infof(false, "is number %d", 2); +errorf(true, "is number %d", 3); +criticalf(true, "is number %d", 4); +fatalf(someFunct(), "is number %d", 5); +-------------------- +*/ +alias tracef = defaultLogFunctionf!(LogLevel.trace); +/// Ditto +alias infof = defaultLogFunctionf!(LogLevel.info); +/// Ditto +alias warningf = defaultLogFunctionf!(LogLevel.warning); +/// Ditto +alias errorf = defaultLogFunctionf!(LogLevel.error); +/// Ditto +alias criticalf = defaultLogFunctionf!(LogLevel.critical); +/// Ditto +alias fatalf = defaultLogFunctionf!(LogLevel.fatal); + +private struct MsgRange +{ + import std.traits : isSomeString, isSomeChar; + + private Logger log; + + this(Logger log) @safe + { + this.log = log; + } + + void put(T)(T msg) @safe + if (isSomeString!T) + { + log.logMsgPart(msg); + } + + void put(dchar elem) @safe + { + import std.utf : encode; + char[4] buffer; + size_t len = encode(buffer, elem); + log.logMsgPart(buffer[0 .. len]); + } +} + +private void formatString(A...)(MsgRange oRange, A args) +{ + import std.format.write : formattedWrite; + + foreach (arg; args) + { + formattedWrite(oRange, "%s", arg); + } +} + +@system unittest +{ + void dummy() @safe + { + auto tl = new TestLogger(); + auto dst = MsgRange(tl); + formatString(dst, "aaa", "bbb"); + } + + dummy(); +} + +/** +There are eight usable logging level. These level are $(I all), $(I trace), +$(I info), $(I warning), $(I error), $(I critical), $(I fatal), and $(I off). +If a log function with `LogLevel.fatal` is called the shutdown handler of +that logger is called. +*/ +enum LogLevel : ubyte +{ + all = 1, /** Lowest possible assignable `LogLevel`. */ + trace = 32, /** `LogLevel` for tracing the execution of the program. */ + info = 64, /** This level is used to display information about the + program. */ + warning = 96, /** warnings about the program should be displayed with this + level. */ + error = 128, /** Information about errors should be logged with this + level.*/ + critical = 160, /** Messages that inform about critical errors should be + logged with this level. */ + fatal = 192, /** Log messages that describe fatal errors should use this + level. */ + off = ubyte.max /** Highest possible `LogLevel`. */ +} + +/** This class is the base of every logger. In order to create a new kind of +logger a deriving class needs to implement the `writeLogMsg` method. By +default this is not thread-safe. + +It is also possible to `override` the three methods `beginLogMsg`, +`logMsgPart` and `finishLogMsg` together, this option gives more +flexibility. +*/ +abstract class Logger +{ + import std.array : appender, Appender; + import std.concurrency : thisTid, Tid; + + /** LogEntry is a aggregation combining all information associated + with a log message. This aggregation will be passed to the method + writeLogMsg. + */ + protected struct LogEntry + { + /// the filename the log function was called from + string file; + /// the line number the log function was called from + int line; + /// the name of the function the log function was called from + string funcName; + /// the pretty formatted name of the function the log function was + /// called from + string prettyFuncName; + /// the name of the module the log message is coming from + string moduleName; + /// the `LogLevel` associated with the log message + LogLevel logLevel; + /// thread id of the log message + Tid threadId; + /// the time the message was logged + SysTime timestamp; + /// the message of the log message + string msg; + /// A refernce to the `Logger` used to create this `LogEntry` + Logger logger; + } + + /** + Every subclass of `Logger` has to call this constructor from their + constructor. It sets the `LogLevel`, and creates a fatal handler. The fatal + handler will throw an `Error` if a log call is made with level + `LogLevel.fatal`. + + Params: + lv = `LogLevel` to use for this `Logger` instance. + */ + this(this This)(LogLevel lv) + { + this.logLevel_ = lv; + this.fatalHandler_ = delegate() { + throw new Error("A fatal log message was logged"); + }; + + this.mutex = new typeof(mutex)(); + } + + /** A custom logger must implement this method in order to work in a + `MultiLogger` and `ArrayLogger`. + + Params: + payload = All information associated with call to log function. + + See_Also: beginLogMsg, logMsgPart, finishLogMsg + */ + abstract protected void writeLogMsg(ref LogEntry payload) @safe; + + /* The default implementation will use an `std.array.appender` + internally to construct the message string. This means dynamic, + GC memory allocation. A logger can avoid this allocation by + reimplementing `beginLogMsg`, `logMsgPart` and `finishLogMsg`. + `beginLogMsg` is always called first, followed by any number of calls + to `logMsgPart` and one call to `finishLogMsg`. + + As an example for such a custom `Logger` compare this: + ---------------- + class CLogger : Logger + { + override void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp) + { + ... logic here + } + + override void logMsgPart(const(char)[] msg) + { + ... logic here + } + + override void finishLogMsg() + { + ... logic here + } + + void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + } + ---------------- + */ + protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + msgAppender = appender!string(); + header = LogEntry(file, line, funcName, prettyFuncName, + moduleName, logLevel, threadId, timestamp, null, logger); + } + + /** Logs a part of the log message. */ + protected void logMsgPart(scope const(char)[] msg) @safe + { + msgAppender.put(msg); + } + + /** Signals that the message has been written and no more calls to + `logMsgPart` follow. */ + protected void finishLogMsg() @safe + { + header.msg = msgAppender.data; + this.writeLogMsg(header); + } + + /** The `LogLevel` determines if the log call are processed or dropped + by the `Logger`. In order for the log call to be processed the + `LogLevel` of the log call must be greater or equal to the `LogLevel` + of the `logger`. + + These two methods set and get the `LogLevel` of the used `Logger`. + + Example: + ----------- + auto f = new FileLogger(stdout); + f.logLevel = LogLevel.info; + assert(f.logLevel == LogLevel.info); + ----------- + */ + @property final LogLevel logLevel() const pure @safe @nogc + { + return trustedLoad(this.logLevel_); + } + + /// Ditto + @property final void logLevel(const LogLevel lv) @safe @nogc + { + atomicStore(this.logLevel_, lv); + } + + /** This `delegate` is called in case a log message with + `LogLevel.fatal` gets logged. + + By default an `Error` will be thrown. + */ + @property final void delegate() fatalHandler() @safe @nogc + { + synchronized (mutex) return this.fatalHandler_; + } + + /// Ditto + @property final void fatalHandler(void delegate() @safe fh) @safe @nogc + { + synchronized (mutex) this.fatalHandler_ = fh; + } + + /** This method allows forwarding log entries from one logger to another. + + `forwardMsg` will ensure proper synchronization and then call + `writeLogMsg`. This is an API for implementing your own loggers and + should not be called by normal user code. A notable difference from other + logging functions is that the `globalLogLevel` wont be evaluated again + since it is assumed that the caller already checked that. + */ + void forwardMsg(ref LogEntry payload) @trusted + { + if (isLoggingEnabled(payload.logLevel, this.logLevel_, + globalLogLevel)) + { + this.writeLogMsg(payload); + + if (payload.logLevel == LogLevel.fatal) + this.fatalHandler_(); + } + } + + /** This template provides the log functions for the `Logger` `class` + with the `LogLevel` encoded in the function name. + + For further information see the the two functions defined inside of this + template. + + The aliases following this template create the public names of these log + functions. + */ + template memLogFunctions(LogLevel ll) + { + /** This function logs data to the used `Logger`. + + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel`. + + Params: + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.trace(1337, "is number"); + s.info(1337, "is number"); + s.error(1337, "is number"); + s.critical(1337, "is number"); + s.fatal(1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if (args.length == 0 || (args.length > 0 && !is(A[0] : bool))) + { + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` depending on a + condition. + + In order for the resulting log message to be logged the `LogLevel` must + be greater or equal than the `LogLevel` of the used `Logger` and + must be greater or equal than the global `LogLevel` additionally the + condition passed must be `true`. + + Params: + condition = The condition must be `true` for the data to be logged. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.trace(true, 1337, "is number"); + s.info(false, 1337, "is number"); + s.error(true, 1337, "is number"); + s.critical(false, 1337, "is number"); + s.fatal(true, 1337, "is number"); + -------------------- + */ + void logImpl(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy A args) + { + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` in a + `printf`-style manner. + + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel` additionally + the passed condition must be `true`. + + Params: + condition = The condition must be `true` for the data to be logged. + msg = The `printf`-style string. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stderr); + s.tracef(true, "is number %d", 1); + s.infof(true, "is number %d", 2); + s.errorf(false, "is number %d", 3); + s.criticalf(someFunc(), "is number %d", 4); + s.fatalf(true, "is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) + { + synchronized (mutex) + { + import std.format.write : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` in a + `printf`-style manner. + + In order for the resulting log message to be logged the `LogLevel` must + be greater or equal than the `LogLevel` of the used `Logger` and + must be greater or equal than the global `LogLevel`. + + Params: + msg = The `printf`-style string. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stderr); + s.tracef("is number %d", 1); + s.infof("is number %d", 2); + s.errorf("is number %d", 3); + s.criticalf("is number %d", 4); + s.fatalf("is number %d", 5); + -------------------- + */ + void logImplf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + { + synchronized (mutex) + { + import std.format.write : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + static if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + } + + /// Ditto + alias trace = memLogFunctions!(LogLevel.trace).logImpl; + /// Ditto + alias tracef = memLogFunctions!(LogLevel.trace).logImplf; + /// Ditto + alias info = memLogFunctions!(LogLevel.info).logImpl; + /// Ditto + alias infof = memLogFunctions!(LogLevel.info).logImplf; + /// Ditto + alias warning = memLogFunctions!(LogLevel.warning).logImpl; + /// Ditto + alias warningf = memLogFunctions!(LogLevel.warning).logImplf; + /// Ditto + alias error = memLogFunctions!(LogLevel.error).logImpl; + /// Ditto + alias errorf = memLogFunctions!(LogLevel.error).logImplf; + /// Ditto + alias critical = memLogFunctions!(LogLevel.critical).logImpl; + /// Ditto + alias criticalf = memLogFunctions!(LogLevel.critical).logImplf; + /// Ditto + alias fatal = memLogFunctions!(LogLevel.fatal).logImpl; + /// Ditto + alias fatalf = memLogFunctions!(LogLevel.fatal).logImplf; + + /** This method logs data with the `LogLevel` of the used `Logger`. + + This method takes a `bool` as first argument. In order for the + data to be processed the `bool` must be `true` and the `LogLevel` + of the Logger must be greater or equal to the global `LogLevel`. + + Params: + args = The data that should be logged. + condition = The condition must be `true` for the data to be logged. + args = The data that is to be logged. + + Returns: The logger used by the logging function as reference. + + Example: + -------------------- + auto l = new StdioLogger(); + l.log(1337); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy A args) + if (args.length != 1) + { + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T, string moduleName = __MODULE__)(const LogLevel ll, + lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__) + { + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` with a specific + `LogLevel`. + + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel`. + + Params: + ll = The specific `LogLevel` used for logging the log message. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.log(LogLevel.trace, 1337, "is number"); + s.log(LogLevel.info, 1337, "is number"); + s.log(LogLevel.warning, 1337, "is number"); + s.log(LogLevel.error, 1337, "is number"); + s.log(LogLevel.fatal, 1337, "is number"); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, lazy A args) + if ((args.length > 1 && !is(Unqual!(A[0]) : bool)) || args.length == 0) + { + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T)(const LogLevel ll, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) + { + synchronized (mutex) + { + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` depending on a + explicitly passed condition with the `LogLevel` of the used + `Logger`. + + In order for the resulting log message to be logged the `LogLevel` + of the used `Logger` must be greater or equal than the global + `LogLevel` and the condition must be `true`. + + Params: + condition = The condition must be `true` for the data to be logged. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(true, 1337, "is number"); + s.log(false, 1337, "is number"); + s.log(false, 1337, "is number"); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, lazy A args) + if (args.length != 1) + { + synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T)(lazy bool condition, lazy T args, int line = __LINE__, + string file = __FILE__, string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) + { + synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` with the `LogLevel` + of the used `Logger`. + + In order for the resulting log message to be logged the `LogLevel` + of the used `Logger` must be greater or equal than the global + `LogLevel`. + + Params: + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.log(1337, "is number"); + s.log(info, 1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + s.log(1337, "is number"); + -------------------- + */ + void log(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy A args) + if ((args.length > 1 + && !is(Unqual!(A[0]) : bool) + && !is(immutable A[0] == immutable LogLevel)) + || args.length == 0) + { + synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /// Ditto + void log(T)(lazy T arg, int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__) + { + synchronized (mutex) + { + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + auto writer = MsgRange(this); + formatString(writer, arg); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` with a specific + `LogLevel` and depending on a condition in a `printf`-style manner. + + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel` and the + condition must be `true`. + + Params: + ll = The specific `LogLevel` used for logging the log message. + condition = The condition must be `true` for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.info, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.warning, true ,"%d %s", 1337, "is number"); + s.logf(LogLevel.error, false ,"%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, true ,"%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy bool condition, lazy string msg, lazy A args) + { + synchronized (mutex) + { + import std.format.write : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel, condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` with a specific + `LogLevel` in a `printf`-style manner. + + In order for the resulting log message to be logged the `LogLevel` + must be greater or equal than the `LogLevel` of the used `Logger` + and must be greater or equal than the global `LogLevel`. + + Params: + ll = The specific `LogLevel` used for logging the log message. + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf(LogLevel.trace, "%d %s", 1337, "is number"); + s.logf(LogLevel.info, "%d %s", 1337, "is number"); + s.logf(LogLevel.warning, "%d %s", 1337, "is number"); + s.logf(LogLevel.error, "%d %s", 1337, "is number"); + s.logf(LogLevel.fatal, "%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(const LogLevel ll, + lazy string msg, lazy A args) + { + synchronized (mutex) + { + import std.format.write : formattedWrite; + + if (isLoggingEnabled(ll, this.logLevel_, globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, ll, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (ll == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This function logs data to the used `Logger` depending on a + condition with the `LogLevel` of the used `Logger` in a + `printf`-style manner. + + In order for the resulting log message to be logged the `LogLevel` + of the used `Logger` must be greater or equal than the global + `LogLevel` and the condition must be `true`. + + Params: + condition = The condition must be `true` for the data to be logged. + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + s.logf(false ,"%d %s", 1337, "is number"); + s.logf(true ,"%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy bool condition, + lazy string msg, lazy A args) + { + synchronized (mutex) + { + import std.format.write : formattedWrite; + + if (isLoggingEnabled(this.logLevel_, this.logLevel_, globalLogLevel, + condition)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + /** This method logs data to the used `Logger` with the `LogLevel` + of the this `Logger` in a `printf`-style manner. + + In order for the data to be processed the `LogLevel` of the `Logger` + must be greater or equal to the global `LogLevel`. + + Params: + msg = The format string used for this log call. + args = The data that should be logged. + + Example: + -------------------- + auto s = new FileLogger(stdout); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + s.logf("%d %s", 1337, "is number"); + -------------------- + */ + void logf(int line = __LINE__, string file = __FILE__, + string funcName = __FUNCTION__, + string prettyFuncName = __PRETTY_FUNCTION__, + string moduleName = __MODULE__, A...)(lazy string msg, lazy A args) + { + synchronized (mutex) + { + import std.format.write : formattedWrite; + + if (isLoggingEnabled(this.logLevel_, this.logLevel_, + globalLogLevel)) + { + this.beginLogMsg(file, line, funcName, prettyFuncName, + moduleName, this.logLevel_, thisTid, Clock.currTime, this); + + auto writer = MsgRange(this); + formattedWrite(writer, msg, args); + + this.finishLogMsg(); + + if (this.logLevel_ == LogLevel.fatal) + this.fatalHandler_(); + } + } + } + + private void delegate() @safe fatalHandler_; + private shared LogLevel logLevel_ = LogLevel.info; + private Mutex mutex; + + protected Appender!string msgAppender; + protected LogEntry header; +} + +// Thread Global + +private shared Logger stdSharedDefaultLogger; +private shared Logger stdSharedLogger; +private shared LogLevel stdLoggerGlobalLogLevel = LogLevel.all; + +/* This method returns the global default Logger. + * Marked @trusted because of excessive reliance on __gshared data + */ +private @property shared(Logger) defaultSharedLoggerImpl() @trusted +{ + import core.lifetime : emplace; + import std.stdio : stderr; + + __gshared align(__traits(classInstanceAlignment, FileLogger)) + void[__traits(classInstanceSize, FileLogger)] _buffer = void; + + import std.concurrency : initOnce; + initOnce!stdSharedDefaultLogger({ + auto buffer = cast(ubyte[]) _buffer; + return cast(shared) emplace!(FileLogger)(buffer, stderr, LogLevel.info); + }()); + + return atomicLoad(stdSharedDefaultLogger); +} + +/** This property sets and gets the default `Logger`. Unless set to another +logger by the user, the default logger's log level is LogLevel.info. + +Example: +------------- +sharedLog = new 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 the used `Logger` is thread-safe. +The default `Logger` is thread-safe. +------------- +if (sharedLog !is myLogger) + sharedLog = new 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)); +} + +/** This methods get and set the global `LogLevel`. + +Every log message with a `LogLevel` lower as 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 threads 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; + } + } + } + + sharedLog = new shared RaceLogger; + scope(exit) { sharedLog = null; } + () @trusted { new Thread(() { log("foo"); }).start(); }(); + log("bar"); +} + +/** 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")); + 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(); +} diff --git a/libphobos/src/std/logger/filelogger.d b/libphobos/src/std/logger/filelogger.d new file mode 100644 index 0000000..c662ca7 --- /dev/null +++ b/libphobos/src/std/logger/filelogger.d @@ -0,0 +1,272 @@ +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/logger/filelogger.d) +*/ +module std.logger.filelogger; + +import std.logger.core; +import std.stdio; + +import std.typecons : Flag; + +/** An option to create $(LREF FileLogger) directory if it is non-existent. +*/ +alias CreateFolder = Flag!"CreateFolder"; + +/** This `Logger` implementation writes log messages to the associated +file. The name of the file has to be passed on construction time. If the file +is already present new log messages will be append at its end. +*/ +class FileLogger : Logger +{ + import std.concurrency : Tid; + import std.datetime.systime : SysTime; + import std.format.write : formattedWrite; + + /** A constructor for the `FileLogger` Logger. + + Params: + fn = The filename of the output file of the `FileLogger`. If that + file can not be opened for writting an exception will be thrown. + lv = The `LogLevel` for the `FileLogger`. By default the + + Example: + ------------- + auto l1 = new FileLogger("logFile"); + auto l2 = new FileLogger("logFile", LogLevel.fatal); + auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes); + ------------- + */ + this(const string fn, const LogLevel lv = LogLevel.all) @safe + { + this(fn, lv, CreateFolder.yes); + } + + /** A constructor for the `FileLogger` Logger that takes a reference to + a `File`. + + The `File` passed must be open for all the log call to the + `FileLogger`. If the `File` gets closed, using the `FileLogger` + for logging will result in undefined behaviour. + + Params: + fn = The file used for logging. + lv = The `LogLevel` for the `FileLogger`. By default the + `LogLevel` for `FileLogger` is `LogLevel.all`. + createFileNameFolder = if yes and fn contains a folder name, this + folder will be created. + + Example: + ------------- + auto file = File("logFile.log", "w"); + auto l1 = new FileLogger(file); + auto l2 = new FileLogger(file, LogLevel.fatal); + ------------- + */ + this(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe + { + import std.file : exists, mkdirRecurse; + import std.path : dirName; + import std.conv : text; + + super(lv); + this.filename = fn; + + if (createFileNameFolder) + { + auto d = dirName(this.filename); + mkdirRecurse(d); + assert(exists(d), text("The folder the FileLogger should have", + " created in '", d,"' could not be created.")); + } + + this.file_.open(this.filename, "a"); + } + + /** A constructor for the `FileLogger` Logger that takes a reference to + a `File`. + + The `File` passed must be open for all the log call to the + `FileLogger`. If the `File` gets closed, using the `FileLogger` + for logging will result in undefined behaviour. + + Params: + file = The file used for logging. + lv = The `LogLevel` for the `FileLogger`. By default the + `LogLevel` for `FileLogger` is `LogLevel.all`. + + Example: + ------------- + auto file = File("logFile.log", "w"); + auto l1 = new FileLogger(file); + auto l2 = new FileLogger(file, LogLevel.fatal); + ------------- + */ + this(File file, const LogLevel lv = LogLevel.all) @safe + { + super(lv); + this.file_ = file; + } + + /** If the `FileLogger` is managing the `File` it logs to, this + method will return a reference to this File. + */ + @property File file() @safe + { + return this.file_; + } + + /* This method overrides the base class method in order to log to a file + without requiring heap allocated memory. Additionally, the `FileLogger` + local mutex is logged to serialize the log calls. + */ + override protected void beginLogMsg(string file, int line, string funcName, + string prettyFuncName, string moduleName, LogLevel logLevel, + Tid threadId, SysTime timestamp, Logger logger) + @safe + { + import std.string : lastIndexOf; + ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; + ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; + + auto lt = this.file_.lockingTextWriter(); + systimeToISOString(lt, timestamp); + import std.conv : to; + formattedWrite(lt, " [%s] %s:%u:%s ", logLevel.to!string, + file[fnIdx .. $], line, funcName[funIdx .. $]); + } + + /* This methods overrides the base class method and writes the parts of + the log call directly to the file. + */ + override protected void logMsgPart(scope const(char)[] msg) + { + formattedWrite(this.file_.lockingTextWriter(), "%s", msg); + } + + /* This methods overrides the base class method and finalizes the active + log call. This requires flushing the `File` and releasing the + `FileLogger` local mutex. + */ + override protected void finishLogMsg() + { + this.file_.lockingTextWriter().put("\n"); + this.file_.flush(); + } + + /* This methods overrides the base class method and delegates the + `LogEntry` data to the actual implementation. + */ + override protected void writeLogMsg(ref LogEntry payload) + { + this.beginLogMsg(payload.file, payload.line, payload.funcName, + payload.prettyFuncName, payload.moduleName, payload.logLevel, + payload.threadId, payload.timestamp, payload.logger); + this.logMsgPart(payload.msg); + this.finishLogMsg(); + } + + /** If the `FileLogger` was constructed with a filename, this method + returns this filename. Otherwise an empty `string` is returned. + */ + string getFilename() + { + return this.filename; + } + + /** The `File` log messages are written to. */ + protected File file_; + + /** The filename of the `File` log messages are written to. */ + protected string filename; +} + +@system unittest +{ + import std.array : empty; + import std.file : deleteme, remove; + import std.string : indexOf; + + string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; + auto l = new FileLogger(filename); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + destroy(l); + + auto file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); +} + +@safe unittest +{ + import std.file : rmdirRecurse, exists, deleteme; + import std.path : dirName; + + const string tmpFolder = dirName(deleteme); + const string filepath = tmpFolder ~ "/bug15771/minas/oops/"; + const string filename = filepath ~ "output.txt"; + assert(!exists(filepath)); + + auto f = new FileLogger(filename, LogLevel.all, CreateFolder.yes); + scope(exit) () @trusted { rmdirRecurse(tmpFolder ~ "/bug15771"); }(); + + f.log("Hello World!"); + assert(exists(filepath)); + f.file.close(); +} + +@system unittest +{ + import std.array : empty; + import std.file : deleteme, remove; + import std.string : indexOf; + + string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; + auto file = File(filename, "w"); + auto l = new FileLogger(file); + + scope(exit) + { + remove(filename); + } + + string notWritten = "this should not be written to file"; + string written = "this should be written to file"; + + l.logLevel = LogLevel.critical; + l.log(LogLevel.warning, notWritten); + l.log(LogLevel.critical, written); + file.close(); + + file = File(filename, "r"); + string readLine = file.readln(); + assert(readLine.indexOf(written) != -1, readLine); + readLine = file.readln(); + assert(readLine.indexOf(notWritten) == -1, readLine); + file.close(); +} + +@system unittest +{ + auto dl = cast(FileLogger) 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; +} diff --git a/libphobos/src/std/logger/multilogger.d b/libphobos/src/std/logger/multilogger.d new file mode 100644 index 0000000..eda922d --- /dev/null +++ b/libphobos/src/std/logger/multilogger.d @@ -0,0 +1,200 @@ +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/logger/multilogger.d) +*/ +module std.logger.multilogger; + +import std.logger.core; +import std.logger.filelogger; + +/** This Element is stored inside the `MultiLogger` and associates a +`Logger` to a `string`. +*/ +struct MultiLoggerEntry +{ + string name; /// The name if the `Logger` + Logger logger; /// The stored `Logger` +} + +/** MultiLogger logs to multiple `Logger`. The `Logger`s are stored in an +`Logger[]` in their order of insertion. + +Every data logged to this `MultiLogger` will be distributed to all the $(D +Logger)s inserted into it. This `MultiLogger` implementation can +hold multiple `Logger`s with the same name. If the method `removeLogger` +is used to remove a `Logger` only the first occurrence with that name will +be removed. +*/ +class MultiLogger : Logger +{ + /** A constructor for the `MultiLogger` Logger. + + Params: + lv = The `LogLevel` for the `MultiLogger`. By default the + `LogLevel` for `MultiLogger` is `LogLevel.all`. + + Example: + ------------- + auto l1 = new MultiLogger(LogLevel.trace); + ------------- + */ + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + } + + /** This member holds all `Logger`s stored in the `MultiLogger`. + + When inheriting from `MultiLogger` this member can be used to gain + access to the stored `Logger`. + */ + protected MultiLoggerEntry[] logger; + + /** This method inserts a new Logger into the `MultiLogger`. + + Params: + name = The name of the `Logger` to insert. + newLogger = The `Logger` to insert. + */ + void insertLogger(string name, Logger newLogger) @safe + { + this.logger ~= MultiLoggerEntry(name, newLogger); + } + + /** This method removes a Logger from the `MultiLogger`. + + Params: + toRemove = The name of the `Logger` to remove. If the `Logger` + is not found `null` will be returned. Only the first occurrence of + a `Logger` with the given name will be removed. + + Returns: The removed `Logger`. + */ + Logger removeLogger(in char[] toRemove) @safe + { + import std.algorithm.mutation : copy; + import std.range.primitives : back, popBack; + for (size_t i = 0; i < this.logger.length; ++i) + { + if (this.logger[i].name == toRemove) + { + Logger ret = this.logger[i].logger; + this.logger[i] = this.logger.back; + this.logger.popBack(); + + return ret; + } + } + + return null; + } + + /* The override to pass the payload to all children of the + `MultiLoggerBase`. + */ + override protected void writeLogMsg(ref LogEntry payload) @safe + { + foreach (it; this.logger) + { + /* We don't perform any checks here to avoid race conditions. + Instead the child will check on its own if its log level matches + and assume LogLevel.all for the globalLogLevel (since we already + know the message passes this test). + */ + it.logger.forwardMsg(payload); + } + } +} + +@safe unittest +{ + import std.exception : assertThrown; + import std.logger.nulllogger; + auto a = new MultiLogger; + auto n0 = new NullLogger(); + auto n1 = new NullLogger(); + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + auto n0_1 = a.removeLogger("zero"); + assert(n0_1 is n0); + auto n = a.removeLogger("zero"); + assert(n is null); + + auto n1_1 = a.removeLogger("one"); + assert(n1_1 is n1); + n = a.removeLogger("one"); + assert(n is null); +} + +@safe unittest +{ + auto a = new MultiLogger; + auto n0 = new TestLogger; + auto n1 = new TestLogger; + a.insertLogger("zero", n0); + a.insertLogger("one", n1); + + a.log("Hello TestLogger"); int line = __LINE__; + assert(n0.msg == "Hello TestLogger"); + assert(n0.line == line); + assert(n1.msg == "Hello TestLogger"); + assert(n1.line == line); +} + +// Issue #16 +@system unittest +{ + import std.file : deleteme; + import std.stdio : File; + import std.string : indexOf; + string logName = deleteme ~ __FUNCTION__ ~ ".log"; + auto logFileOutput = File(logName, "w"); + scope(exit) + { + import std.file : remove; + logFileOutput.close(); + remove(logName); + } + auto traceLog = new FileLogger(logFileOutput, LogLevel.all); + auto infoLog = new TestLogger(LogLevel.info); + + auto root = new MultiLogger(LogLevel.all); + root.insertLogger("fileLogger", traceLog); + root.insertLogger("stdoutLogger", infoLog); + + string tMsg = "A trace message"; + root.trace(tMsg); int line1 = __LINE__; + + assert(infoLog.line != line1); + assert(infoLog.msg != tMsg); + + string iMsg = "A info message"; + root.info(iMsg); int line2 = __LINE__; + + assert(infoLog.line == line2); + assert(infoLog.msg == iMsg, infoLog.msg ~ ":" ~ iMsg); + + logFileOutput.close(); + logFileOutput = File(logName, "r"); + assert(logFileOutput.isOpen); + assert(!logFileOutput.eof); + + auto line = logFileOutput.readln(); + assert(line.indexOf(tMsg) != -1, line ~ ":" ~ tMsg); + assert(!logFileOutput.eof); + line = logFileOutput.readln(); + assert(line.indexOf(iMsg) != -1, line ~ ":" ~ tMsg); +} + +@system unittest +{ + auto dl = cast(FileLogger) 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; +} diff --git a/libphobos/src/std/logger/nulllogger.d b/libphobos/src/std/logger/nulllogger.d new file mode 100644 index 0000000..fd48b85 --- /dev/null +++ b/libphobos/src/std/logger/nulllogger.d @@ -0,0 +1,41 @@ +// Written in the D programming language. +/** +Source: $(PHOBOSSRC std/logger/nulllogger.d) +*/ +module std.logger.nulllogger; + +import std.logger.core; + +/** The `NullLogger` will not process any log messages. + +In case of a log message with `LogLevel.fatal` nothing will happen. +*/ +class NullLogger : Logger +{ + /** The default constructor for the `NullLogger`. + + Independent of the parameter this Logger will never log a message. + + Params: + lv = The `LogLevel` for the `NullLogger`. By default the `LogLevel` + for `NullLogger` is `LogLevel.all`. + */ + this(const LogLevel lv = LogLevel.all) @safe + { + super(lv); + this.fatalHandler = delegate() {}; + } + + override protected void writeLogMsg(ref LogEntry payload) @safe @nogc + { + } +} + +/// +@safe unittest +{ + import std.logger.core : LogLevel; + auto nl1 = new NullLogger(LogLevel.all); + nl1.info("You will never read this."); + nl1.fatal("You will never read this, either and it will not throw"); +} diff --git a/libphobos/src/std/logger/package.d b/libphobos/src/std/logger/package.d new file mode 100644 index 0000000..4f4183c --- /dev/null +++ b/libphobos/src/std/logger/package.d @@ -0,0 +1,168 @@ +// Written in the D programming language. +/** +Implements logging facilities. + +Copyright: Copyright Robert "burner" Schadek 2013 -- +License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>. +Authors: $(HTTP www.svs.informatik.uni-oldenburg.de/60865.html, Robert burner Schadek) + +$(H3 Basic Logging) + +Message logging is a common approach to expose runtime information of a +program. Logging should be easy, but also flexible and powerful, therefore +`D` provides a standard interface for logging. + +The easiest way to create a log message is to write: +------------- +import std.logger; + +void main() { + log("Hello World"); +} +------------- +This will print a message to the `stderr` device. The message will contain +the filename, the line number, the name of the surrounding function, the time +and the message. + +More complex log call can go along the lines like: +------------- +log("Logging to the sharedLog with its default LogLevel"); +logf(LogLevel.info, 5 < 6, "%s to the sharedLog with its LogLevel.info", "Logging"); +info("Logging to the sharedLog with its info LogLevel"); +warning(5 < 6, "Logging to the sharedLog with its LogLevel.warning if 5 is less than 6"); +error("Logging to the sharedLog with its error LogLevel"); +errorf("Logging %s the sharedLog %s its error LogLevel", "to", "with"); +critical("Logging to the"," sharedLog with its error LogLevel"); +fatal("Logging to the sharedLog with its fatal LogLevel"); + +auto fLogger = new FileLogger("NameOfTheLogFile"); +fLogger.log("Logging to the fileLogger with its default LogLevel"); +fLogger.info("Logging to the fileLogger with its default LogLevel"); +fLogger.warning(5 < 6, "Logging to the fileLogger with its LogLevel.warning if 5 is less than 6"); +fLogger.warningf(5 < 6, "Logging to the fileLogger with its LogLevel.warning if %s is %s than 6", 5, "less"); +fLogger.critical("Logging to the fileLogger with its info LogLevel"); +fLogger.log(LogLevel.trace, 5 < 6, "Logging to the fileLogger"," with its default LogLevel if 5 is less than 6"); +fLogger.fatal("Logging to the fileLogger with its warning LogLevel"); +------------- +Additionally, this example shows how a new `FileLogger` is created. +Individual `Logger` and the global log functions share commonly named +functions to log data. + +The names of the functions are as follows: +$(UL + $(LI `log`) + $(LI `trace`) + $(LI `info`) + $(LI `warning`) + $(LI `critical`) + $(LI `fatal`) +) +The default `Logger` will by default log to `stderr` and has a default +`LogLevel` of `LogLevel.all`. The default Logger can be accessed by +using the property called `sharedLog`. This property is a reference to the +current default `Logger`. This reference can be used to assign a new +default `Logger`. +------------- +sharedLog = new FileLogger("New_Default_Log_File.log"); +------------- + +Additional `Logger` can be created by creating a new instance of the +required `Logger`. + +$(H3 Logging Fundamentals) +$(H4 LogLevel) +The `LogLevel` of a log call can be defined in two ways. The first is by +calling `log` and passing the `LogLevel` explicitly as the first argument. +The second way of setting the `LogLevel` of a +log call, is by calling either `trace`, `info`, `warning`, +`critical`, or `fatal`. The log call will then have the respective +`LogLevel`. If no `LogLevel` is defined the log call will use the +current `LogLevel` of the used `Logger`. If data is logged with +`LogLevel` `fatal` by default an `Error` will be thrown. +This behaviour can be modified by using the member `fatalHandler` to +assign a custom delegate to handle log call with `LogLevel` `fatal`. + +$(H4 Conditional Logging) +Conditional logging can be achieved be passing a `bool` as first +argument to a log function. If conditional logging is used the condition must +be `true` in order to have the log message logged. + +In order to combine an explicit `LogLevel` passing with conditional +logging, the `LogLevel` has to be passed as first argument followed by the +`bool`. + +$(H4 Filtering Log Messages) +Messages are logged if the `LogLevel` of the log message is greater than or +equal to the `LogLevel` of the used `Logger` and additionally if the +`LogLevel` of the log message is greater than or equal to the global `LogLevel`. +If a condition is passed into the log call, this condition must be true. + +The global `LogLevel` is accessible by using `globalLogLevel`. +To assign a `LogLevel` of a `Logger` use the `logLevel` property of +the logger. + +$(H4 Printf Style Logging) +If `printf`-style logging is needed add a $(B f) to the logging call, such as +$(D myLogger.infof("Hello %s", "world");) or $(D fatalf("errno %d", 1337)). +The additional $(B f) appended to the function name enables `printf`-style +logging for all combinations of explicit `LogLevel` and conditional +logging functions and methods. + +$(H4 Thread Local Redirection) +Calls to the free standing log functions are not directly forwarded to the +global `Logger` `sharedLog`. Actually, a thread local `Logger` of +type `StdForwardLogger` processes the log call and then, by default, forwards +the created `Logger.LogEntry` to the `sharedLog` `Logger`. +The thread local `Logger` is accessible by the `stdThreadLocalLog` +property. This property allows to assign user defined `Logger`. The default +`LogLevel` of the `stdThreadLocalLog` `Logger` is `LogLevel.all` +and it will therefore forward all messages to the `sharedLog` `Logger`. +The `LogLevel` of the `stdThreadLocalLog` can be used to filter log +calls before they reach the `sharedLog` `Logger`. + +$(H3 User Defined Logger) +To customize the `Logger` behavior, create a new `class` that inherits from +the abstract `Logger` `class`, and implements the `writeLogMsg` +method. +------------- +class MyCustomLogger : Logger +{ + this(LogLevel lv) @safe + { + super(lv); + } + + override void writeLogMsg(ref LogEntry payload) + { + // log message in my custom way + } +} + +auto logger = new MyCustomLogger(LogLevel.info); +logger.log("Awesome log message with LogLevel.info"); +------------- + +To gain more precise control over the logging process, additionally to +overriding the `writeLogMsg` method the methods `beginLogMsg`, +`logMsgPart` and `finishLogMsg` can be overridden. + +$(H3 Provided Logger) +By default four `Logger` implementations are given. The `FileLogger` +logs data to files. It can also be used to log to `stdout` and `stderr` +as these devices are files as well. A `Logger` that logs to `stdout` can +therefore be created by $(D new FileLogger(stdout)). +The `MultiLogger` is basically an associative array of `string`s to +`Logger`. It propagates log calls to its stored `Logger`. The +`ArrayLogger` contains an array of `Logger` and also propagates log +calls to its stored `Logger`. The `NullLogger` does not do anything. It +will never log a message and will never throw on a log call with `LogLevel` +`error`. + +Source: $(PHOBOSSRC std/logger/package.d) +*/ +module std.logger; + +public import std.logger.core; +public import std.logger.filelogger; +public import std.logger.multilogger; +public import std.logger.nulllogger; diff --git a/libphobos/src/std/meta.d b/libphobos/src/std/meta.d index 0075ed7..2db341d 100644 --- a/libphobos/src/std/meta.d +++ b/libphobos/src/std/meta.d @@ -627,35 +627,9 @@ Evaluates to `AliasSeq!(fun!(args[0]), fun!(args[1]), ..., fun!(args[$ - 1]))`. */ template staticMap(alias fun, args...) { - version (__staticMap_simplest_but_buggy) - { - // @@@ BUG @@@ - // The following straightforward implementation exposes a bug. - // See issue https://issues.dlang.org/show_bug.cgi?id=22421 and unittest below. - alias staticMap = AliasSeq!(); - static foreach (arg; args) - staticMap = AliasSeq!(staticMap, fun!arg); - } - else version (__staticMap_simple_but_slow) - { - // This has a performance bug. Appending to the staticMap seems to be quadratic. - alias staticMap = AliasSeq!(); - static foreach (i; 0 .. args.length) - staticMap = AliasSeq!(staticMap, fun!(args[i])); - } - else // Current best-of-breed implementation imitates quicksort - { - static if (args.length <= 8) - { - alias staticMap = AliasSeq!(); - static foreach (i; 0 .. args.length) - staticMap = AliasSeq!(staticMap, fun!(args[i])); - } - else - { - alias staticMap = AliasSeq!(staticMap!(fun, args[0 .. $ / 2]), staticMap!(fun, args[$ / 2 .. $])); - } - } + alias staticMap = AliasSeq!(); + static foreach (arg; args) + staticMap = AliasSeq!(staticMap, fun!arg); } /// diff --git a/libphobos/src/std/package.d b/libphobos/src/std/package.d index bfb135b..e2eca7b 100644 --- a/libphobos/src/std/package.d +++ b/libphobos/src/std/package.d @@ -53,6 +53,7 @@ public import std.getopt, std.int128, std.json, + std.logger, std.math, std.mathspecial, std.meta, diff --git a/libphobos/src/std/random.d b/libphobos/src/std/random.d index b2206ce..70b87dd 100644 --- a/libphobos/src/std/random.d +++ b/libphobos/src/std/random.d @@ -2806,7 +2806,7 @@ auto ref choice(Range)(ref Range range) { import std.algorithm.searching : canFind; - class MyTestClass + static class MyTestClass { int x; diff --git a/libphobos/src/std/regex/package.d b/libphobos/src/std/regex/package.d index e24abc5..1562d79 100644 --- a/libphobos/src/std/regex/package.d +++ b/libphobos/src/std/regex/package.d @@ -43,11 +43,16 @@ $(TR $(TD Objects) $(TD )) $(SECTION Synopsis) - --- - import std.regex; - import std.stdio; - void main() - { + + Create a regex at runtime: + $(RUNNABLE_EXAMPLE + $(RUNNABLE_EXAMPLE_STDIN +They met on 24/01/1970. +7/8/99 wasn't as hot as 7/8/2022. +) + --- + import std.regex; + import std.stdio; // Print out all possible dd/mm/yy(yy) dates found in user input. auto r = regex(r"\b[0-9][0-9]?/[0-9][0-9]?/[0-9][0-9](?:[0-9][0-9])?\b"); foreach (line; stdin.byLine) @@ -57,19 +62,24 @@ $(TR $(TD Objects) $(TD foreach (c; matchAll(line, r)) writeln(c.hit); } - } - ... - - // Create a static regex at compile-time, which contains fast native code. + --- + ) + Create a static regex at compile-time, which contains fast native code: + $(RUNNABLE_EXAMPLE + --- + import std.regex; auto ctr = ctRegex!(`^.*/([^/]+)/?$`); // It works just like a normal regex: auto c2 = matchFirst("foo/bar", ctr); // First match found here, if any assert(!c2.empty); // Be sure to check if there is a match before examining contents! assert(c2[1] == "bar"); // Captures is a range of submatches: 0 = full match. - - ... - // multi-pattern regex + --- + ) + Multi-pattern regex: + $(RUNNABLE_EXAMPLE + --- + import std.regex; auto multi = regex([`\d+,\d+`, `([a-z]+):(\d+)`]); auto m = "abc:43 12,34".matchAll(multi); assert(m.front.whichPattern == 2); @@ -77,21 +87,27 @@ $(TR $(TD Objects) $(TD assert(m.front[2] == "43"); m.popFront(); assert(m.front.whichPattern == 1); - assert(m.front[1] == "12"); - ... - + assert(m.front[0] == "12,34"); + --- + ) + $(LREF Captures) and `opCast!bool`: + $(RUNNABLE_EXAMPLE + --- + import std.regex; // The result of `matchAll/matchFirst` is directly testable with `if/assert/while`, // e.g. test if a string consists of letters only: assert(matchFirst("LettersOnly", `^\p{L}+$`)); - // And we can take advantage of the ability to define a variable in the $(LINK2 https://dlang.org/spec/statement.html#IfCondition `IfCondition`): - if (const auto captures = matchFirst("At l34st one digit, but maybe more...", `((\d)(\d*))`)) + // And we can take advantage of the ability to define a variable in the IfCondition: + if (const captures = matchFirst("At l34st one digit, but maybe more...", `((\d)(\d*))`)) { assert(captures[2] == "3"); assert(captures[3] == "4"); assert(captures[1] == "34"); } --- + ) + See_Also: $(LINK2 https://dlang.org/spec/statement.html#IfCondition, `IfCondition`). $(SECTION Syntax and general information) The general usage guideline is to keep regex complexity on the side of simplicity, @@ -470,7 +486,7 @@ private struct CTRegexWrapper(Char) alias getRe this; } -template ctRegexImpl(alias pattern, string flags=[]) +template ctRegexImpl(alias pattern, string flags="") { import std.regex.internal.backtracking, std.regex.internal.parser; static immutable r = cast(immutable) regex(pattern, flags); @@ -518,7 +534,7 @@ template ctRegexImpl(alias pattern, string flags=[]) pattern = Regular expression flags = The _attributes (g, i, m, s and x accepted) +/ -public enum ctRegex(alias pattern, alias flags=[]) = ctRegexImpl!(pattern, flags).wrapper; +public enum ctRegex(alias pattern, string flags="") = ctRegexImpl!(pattern, flags).wrapper; enum isRegexFor(RegEx, R) = is(immutable RegEx == immutable Regex!(BasicElementOf!R)) || is(RegEx : const(Regex!(BasicElementOf!R))) diff --git a/libphobos/src/std/string.d b/libphobos/src/std/string.d index 5c9a2c9..7ed24f7 100644 --- a/libphobos/src/std/string.d +++ b/libphobos/src/std/string.d @@ -6884,7 +6884,7 @@ if (isSomeString!S) if (inword) { - if (col + 1 + (s.length - wordstart) >= columns) + if (col + 1 + (s.length - wordstart) > columns) { result ~= '\n'; result ~= indent; @@ -6929,6 +6929,13 @@ if (isSomeString!S) }); } +@safe pure unittest // https://issues.dlang.org/show_bug.cgi?id=23298 +{ + assert("1 2 3 4 5 6 7 8 9".wrap(17) == "1 2 3 4 5 6 7 8 9\n"); + assert("1 2 3 4 5 6 7 8 9 ".wrap(17) == "1 2 3 4 5 6 7 8 9\n"); + assert("1 2 3 4 5 6 7 8 99".wrap(17) == "1 2 3 4 5 6 7 8\n99\n"); +} + /****************************************** * Removes one level of indentation from a multi-line string. * diff --git a/libphobos/src/std/typecons.d b/libphobos/src/std/typecons.d index 4ecfb10..c7cdc24 100644 --- a/libphobos/src/std/typecons.d +++ b/libphobos/src/std/typecons.d @@ -1146,7 +1146,7 @@ if (distinctFieldNames!(Specs)) } /// - static if (Specs.length == 0) @safe unittest + static if (Specs.length == 0) @system unittest { //replace names by their position @@ -1166,7 +1166,7 @@ if (distinctFieldNames!(Specs)) assert(t2Named.c == 3); } - static if (Specs.length == 0) @safe unittest + static if (Specs.length == 0) @system unittest { //check that empty translations work fine enum string[string] a0 = null; |