1 // Written in the D programming language.
2 /**
3 Source: $(PHOBOSSRC std/logger/multilogger.d)
4 */
5 module std.logger.multilogger;
6 
7 import std.logger.core;
8 import std.logger.filelogger;
9 
10 /** This Element is stored inside the `MultiLogger` and associates a
11 `Logger` to a `string`.
12 */
13 struct MultiLoggerEntry
14 {
15     string name; /// The name if the `Logger`
16     Logger logger; /// The stored `Logger`
17 }
18 
19 /** MultiLogger logs to multiple `Logger`. The `Logger`s are stored in an
20 `Logger[]` in their order of insertion.
21 
22 Every data logged to this `MultiLogger` will be distributed to all the $(D
23 Logger)s inserted into it. This `MultiLogger` implementation can
24 hold multiple `Logger`s with the same name. If the method `removeLogger`
25 is used to remove a `Logger` only the first occurrence with that name will
26 be removed.
27 */
28 class MultiLogger : Logger
29 {
30     /** A constructor for the `MultiLogger` Logger.
31 
32     Params:
33       lv = The `LogLevel` for the `MultiLogger`. By default the
34       `LogLevel` for `MultiLogger` is `LogLevel.all`.
35 
36     Example:
37     -------------
38     auto l1 = new MultiLogger(LogLevel.trace);
39     -------------
40     */
41     this(const LogLevel lv = LogLevel.all) @safe
42     {
43         super(lv);
44     }
45 
46     /** This member holds all `Logger`s stored in the `MultiLogger`.
47 
48     When inheriting from `MultiLogger` this member can be used to gain
49     access to the stored `Logger`.
50     */
51     protected MultiLoggerEntry[] logger;
52 
53     /** This method inserts a new Logger into the `MultiLogger`.
54 
55     Params:
56       name = The name of the `Logger` to insert.
57       newLogger = The `Logger` to insert.
58     */
59     void insertLogger(string name, Logger newLogger) @safe
60     {
61         this.logger ~= MultiLoggerEntry(name, newLogger);
62     }
63 
64     /** This method removes a Logger from the `MultiLogger`.
65 
66     Params:
67       toRemove = The name of the `Logger` to remove. If the `Logger`
68         is not found `null` will be returned. Only the first occurrence of
69         a `Logger` with the given name will be removed.
70 
71     Returns: The removed `Logger`.
72     */
73     Logger removeLogger(in char[] toRemove) @safe
74     {
75         import std.algorithm.mutation : copy;
76         import std.range.primitives : back, popBack;
77         for (size_t i = 0; i < this.logger.length; ++i)
78         {
79             if (this.logger[i].name == toRemove)
80             {
81                 Logger ret = this.logger[i].logger;
82                 this.logger[i] = this.logger.back;
83                 this.logger.popBack();
84 
85                 return ret;
86             }
87         }
88 
89         return null;
90     }
91 
92     /* The override to pass the payload to all children of the
93     `MultiLoggerBase`.
94     */
95     override protected void writeLogMsg(ref LogEntry payload) @safe
96     {
97         foreach (it; this.logger)
98         {
99             /* We don't perform any checks here to avoid race conditions.
100             Instead the child will check on its own if its log level matches
101             and assume LogLevel.all for the globalLogLevel (since we already
102             know the message passes this test).
103             */
104             it.logger.forwardMsg(payload);
105         }
106     }
107 }
108 
109 @safe unittest
110 {
111     import std.exception : assertThrown;
112     import std.logger.nulllogger;
113     auto a = new MultiLogger;
114     auto n0 = new NullLogger();
115     auto n1 = new NullLogger();
116     a.insertLogger("zero", n0);
117     a.insertLogger("one", n1);
118 
119     auto n0_1 = a.removeLogger("zero");
120     assert(n0_1 is n0);
121     auto n = a.removeLogger("zero");
122     assert(n is null);
123 
124     auto n1_1 = a.removeLogger("one");
125     assert(n1_1 is n1);
126     n = a.removeLogger("one");
127     assert(n is null);
128 }
129 
130 @safe unittest
131 {
132     auto a = new MultiLogger;
133     auto n0 = new TestLogger;
134     auto n1 = new TestLogger;
135     a.insertLogger("zero", n0);
136     a.insertLogger("one", n1);
137 
138     a.log("Hello TestLogger"); int line = __LINE__;
139     assert(n0.msg == "Hello TestLogger");
140     assert(n0.line == line);
141     assert(n1.msg == "Hello TestLogger");
142     assert(n1.line == line);
143 }
144 
145 // Issue #16
146 @system unittest
147 {
148     import std.file : deleteme;
149     import std.stdio : File;
150     import std.string : indexOf;
151     string logName = deleteme ~ __FUNCTION__ ~ ".log";
152     auto logFileOutput = File(logName, "w");
153     scope(exit)
154     {
155         import std.file : remove;
156         logFileOutput.close();
157         remove(logName);
158     }
159     auto traceLog = new FileLogger(logFileOutput, LogLevel.all);
160     auto infoLog  = new TestLogger(LogLevel.info);
161 
162     auto root = new MultiLogger(LogLevel.all);
163     root.insertLogger("fileLogger", traceLog);
164     root.insertLogger("stdoutLogger", infoLog);
165 
166     string tMsg = "A trace message";
167     root.trace(tMsg); int line1 = __LINE__;
168 
169     assert(infoLog.line != line1);
170     assert(infoLog.msg != tMsg);
171 
172     string iMsg = "A info message";
173     root.info(iMsg); int line2 = __LINE__;
174 
175     assert(infoLog.line == line2);
176     assert(infoLog.msg == iMsg, infoLog.msg ~ ":" ~ iMsg);
177 
178     logFileOutput.close();
179     logFileOutput = File(logName, "r");
180     assert(logFileOutput.isOpen);
181     assert(!logFileOutput.eof);
182 
183     auto line = logFileOutput.readln();
184     assert(line.indexOf(tMsg) != -1, line ~ ":" ~ tMsg);
185     assert(!logFileOutput.eof);
186     line = logFileOutput.readln();
187     assert(line.indexOf(iMsg) != -1, line ~ ":" ~ tMsg);
188 }
189 
190 @system unittest
191 {
192     auto dl = cast(FileLogger) sharedLog;
193     assert(dl !is null);
194     assert(dl.logLevel == LogLevel.info);
195     assert(globalLogLevel == LogLevel.all);
196 
197     auto tl = cast(StdForwardLogger) stdThreadLocalLog;
198     assert(tl !is null);
199     stdThreadLocalLog.logLevel = LogLevel.all;
200 }