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 }