]>
Commit | Line | Data |
---|---|---|
5fee5ec3 IB |
1 | // Written in the D programming language. |
2 | /** | |
3 | Source: $(PHOBOSSRC std/experimental/logger/multilogger.d) | |
4 | */ | |
b4c522fa IB |
5 | module std.experimental.logger.multilogger; |
6 | ||
7 | import std.experimental.logger.core; | |
8 | import std.experimental.logger.filelogger; | |
9 | ||
5fee5ec3 IB |
10 | /** This Element is stored inside the `MultiLogger` and associates a |
11 | `Logger` to a `string`. | |
b4c522fa IB |
12 | */ |
13 | struct MultiLoggerEntry | |
14 | { | |
5fee5ec3 IB |
15 | string name; /// The name if the `Logger` |
16 | Logger logger; /// The stored `Logger` | |
b4c522fa IB |
17 | } |
18 | ||
5fee5ec3 IB |
19 | /** MultiLogger logs to multiple `Logger`. The `Logger`s are stored in an |
20 | `Logger[]` in their order of insertion. | |
b4c522fa | 21 | |
5fee5ec3 IB |
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 | |
b4c522fa IB |
26 | be removed. |
27 | */ | |
28 | class MultiLogger : Logger | |
29 | { | |
5fee5ec3 | 30 | /** A constructor for the `MultiLogger` Logger. |
b4c522fa IB |
31 | |
32 | Params: | |
5fee5ec3 IB |
33 | lv = The `LogLevel` for the `MultiLogger`. By default the |
34 | `LogLevel` for `MultiLogger` is `LogLevel.all`. | |
b4c522fa IB |
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 | ||
5fee5ec3 | 46 | /** This member holds all `Logger`s stored in the `MultiLogger`. |
b4c522fa | 47 | |
5fee5ec3 IB |
48 | When inheriting from `MultiLogger` this member can be used to gain |
49 | access to the stored `Logger`. | |
b4c522fa IB |
50 | */ |
51 | protected MultiLoggerEntry[] logger; | |
52 | ||
5fee5ec3 | 53 | /** This method inserts a new Logger into the `MultiLogger`. |
b4c522fa IB |
54 | |
55 | Params: | |
5fee5ec3 IB |
56 | name = The name of the `Logger` to insert. |
57 | newLogger = The `Logger` to insert. | |
b4c522fa IB |
58 | */ |
59 | void insertLogger(string name, Logger newLogger) @safe | |
60 | { | |
61 | this.logger ~= MultiLoggerEntry(name, newLogger); | |
62 | } | |
63 | ||
5fee5ec3 | 64 | /** This method removes a Logger from the `MultiLogger`. |
b4c522fa IB |
65 | |
66 | Params: | |
5fee5ec3 IB |
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. | |
b4c522fa | 70 | |
5fee5ec3 | 71 | Returns: The removed `Logger`. |
b4c522fa IB |
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 | |
5fee5ec3 | 93 | `MultiLoggerBase`. |
b4c522fa IB |
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.experimental.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 | @safe unittest | |
191 | { | |
192 | auto dl = cast(FileLogger) sharedLog; | |
193 | assert(dl !is null); | |
194 | assert(dl.logLevel == LogLevel.all); | |
195 | assert(globalLogLevel == LogLevel.all); | |
196 | ||
197 | auto tl = cast(StdForwardLogger) stdThreadLocalLog; | |
198 | assert(tl !is null); | |
199 | stdThreadLocalLog.logLevel = LogLevel.all; | |
200 | } |