summaryrefslogtreecommitdiff
path: root/libphobos/src/std/experimental/logger/multilogger.d
blob: 90bfb5820ab0e9aea1e488aff4582fc52c133729 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// Written in the D programming language.
/**
Source: $(PHOBOSSRC std/experimental/logger/multilogger.d)
*/
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);
}

@safe unittest
{
    auto dl = cast(FileLogger) sharedLog;
    assert(dl !is null);
    assert(dl.logLevel == LogLevel.warning);
    assert(globalLogLevel == LogLevel.all);

    auto tl = cast(StdForwardLogger) stdThreadLocalLog;
    assert(tl !is null);
    stdThreadLocalLog.logLevel = LogLevel.all;
}