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