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