1 /// 2 module std.experimental.logger.filelogger; 3 4 import std.experimental.logger.core; 5 import std.stdio; 6 7 import std.typecons : Flag; 8 9 /** An option to create $(LREF FileLogger) directory if it is non-existent. 10 */ 11 alias CreateFolder = Flag!"CreateFolder"; 12 13 /** This $(D Logger) implementation writes log messages to the associated 14 file. The name of the file has to be passed on construction time. If the file 15 is already present new log messages will be append at its end. 16 */ 17 class FileLogger : Logger 18 { 19 import std.concurrency : Tid; 20 import std.datetime.systime : SysTime; 21 import std.format : formattedWrite; 22 23 /** A constructor for the $(D FileLogger) Logger. 24 25 Params: 26 fn = The filename of the output file of the $(D FileLogger). If that 27 file can not be opened for writting an exception will be thrown. 28 lv = The $(D LogLevel) for the $(D FileLogger). By default the 29 30 Example: 31 ------------- 32 auto l1 = new FileLogger("logFile"); 33 auto l2 = new FileLogger("logFile", LogLevel.fatal); 34 auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes); 35 ------------- 36 */ 37 this(in string fn, const LogLevel lv = LogLevel.all) @safe 38 { 39 this(fn, lv, CreateFolder.yes); 40 } 41 42 /** A constructor for the $(D FileLogger) Logger that takes a reference to 43 a $(D File). 44 45 The $(D File) passed must be open for all the log call to the 46 $(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger) 47 for logging will result in undefined behaviour. 48 49 Params: 50 fn = The file used for logging. 51 lv = The $(D LogLevel) for the $(D FileLogger). By default the 52 $(D LogLevel) for $(D FileLogger) is $(D LogLevel.all). 53 createFileNameFolder = if yes and fn contains a folder name, this 54 folder will be created. 55 56 Example: 57 ------------- 58 auto file = File("logFile.log", "w"); 59 auto l1 = new FileLogger(file); 60 auto l2 = new FileLogger(file, LogLevel.fatal); 61 ------------- 62 */ this(in string fn,const LogLevel lv,CreateFolder createFileNameFolder)63 this(in string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe 64 { 65 import std.file : exists, mkdirRecurse; 66 import std.path : dirName; 67 import std.conv : text; 68 69 super(lv); 70 this.filename = fn; 71 72 if (createFileNameFolder) 73 { 74 auto d = dirName(this.filename); 75 mkdirRecurse(d); 76 assert(exists(d), text("The folder the FileLogger should have", 77 " created in '", d,"' could not be created.")); 78 } 79 80 this.file_.open(this.filename, "a"); 81 } 82 83 /** A constructor for the $(D FileLogger) Logger that takes a reference to 84 a $(D File). 85 86 The $(D File) passed must be open for all the log call to the 87 $(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger) 88 for logging will result in undefined behaviour. 89 90 Params: 91 file = The file used for logging. 92 lv = The $(D LogLevel) for the $(D FileLogger). By default the 93 $(D LogLevel) for $(D FileLogger) is $(D LogLevel.all). 94 95 Example: 96 ------------- 97 auto file = File("logFile.log", "w"); 98 auto l1 = new FileLogger(file); 99 auto l2 = new FileLogger(file, LogLevel.fatal); 100 ------------- 101 */ 102 this(File file, const LogLevel lv = LogLevel.all) @safe 103 { 104 super(lv); 105 this.file_ = file; 106 } 107 108 /** If the $(D FileLogger) is managing the $(D File) it logs to, this 109 method will return a reference to this File. 110 */ file()111 @property File file() @safe 112 { 113 return this.file_; 114 } 115 116 /* This method overrides the base class method in order to log to a file 117 without requiring heap allocated memory. Additionally, the $(D FileLogger) 118 local mutex is logged to serialize the log calls. 119 */ beginLogMsg(string file,int line,string funcName,string prettyFuncName,string moduleName,LogLevel logLevel,Tid threadId,SysTime timestamp,Logger logger)120 override protected void beginLogMsg(string file, int line, string funcName, 121 string prettyFuncName, string moduleName, LogLevel logLevel, 122 Tid threadId, SysTime timestamp, Logger logger) 123 @safe 124 { 125 import std.string : lastIndexOf; 126 ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; 127 ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; 128 129 auto lt = this.file_.lockingTextWriter(); 130 systimeToISOString(lt, timestamp); 131 formattedWrite(lt, ":%s:%s:%u ", file[fnIdx .. $], 132 funcName[funIdx .. $], line); 133 } 134 135 /* This methods overrides the base class method and writes the parts of 136 the log call directly to the file. 137 */ logMsgPart(const (char)[]msg)138 override protected void logMsgPart(const(char)[] msg) 139 { 140 formattedWrite(this.file_.lockingTextWriter(), "%s", msg); 141 } 142 143 /* This methods overrides the base class method and finalizes the active 144 log call. This requires flushing the $(D File) and releasing the 145 $(D FileLogger) local mutex. 146 */ finishLogMsg()147 override protected void finishLogMsg() 148 { 149 this.file_.lockingTextWriter().put("\n"); 150 this.file_.flush(); 151 } 152 153 /* This methods overrides the base class method and delegates the 154 $(D LogEntry) data to the actual implementation. 155 */ writeLogMsg(ref LogEntry payload)156 override protected void writeLogMsg(ref LogEntry payload) 157 { 158 this.beginLogMsg(payload.file, payload.line, payload.funcName, 159 payload.prettyFuncName, payload.moduleName, payload.logLevel, 160 payload.threadId, payload.timestamp, payload.logger); 161 this.logMsgPart(payload.msg); 162 this.finishLogMsg(); 163 } 164 165 /** If the $(D FileLogger) was constructed with a filename, this method 166 returns this filename. Otherwise an empty $(D string) is returned. 167 */ getFilename()168 string getFilename() 169 { 170 return this.filename; 171 } 172 173 private File file_; 174 private string filename; 175 } 176 177 @system unittest 178 { 179 import std.array : empty; 180 import std.file : deleteme, remove; 181 import std.string : indexOf; 182 183 string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 184 auto l = new FileLogger(filename); 185 scope(exit)186 scope(exit) 187 { 188 remove(filename); 189 } 190 191 string notWritten = "this should not be written to file"; 192 string written = "this should be written to file"; 193 194 l.logLevel = LogLevel.critical; 195 l.log(LogLevel.warning, notWritten); 196 l.log(LogLevel.critical, written); 197 destroy(l); 198 199 auto file = File(filename, "r"); 200 string readLine = file.readln(); 201 assert(readLine.indexOf(written) != -1, readLine); 202 readLine = file.readln(); 203 assert(readLine.indexOf(notWritten) == -1, readLine); 204 } 205 206 @safe unittest 207 { 208 import std.file : rmdirRecurse, exists, deleteme; 209 import std.path : dirName; 210 211 const string tmpFolder = dirName(deleteme); 212 const string filepath = tmpFolder ~ "/bug15771/minas/oops/"; 213 const string filename = filepath ~ "output.txt"; 214 assert(!exists(filepath)); 215 216 auto f = new FileLogger(filename, LogLevel.all, CreateFolder.yes); scope(exit)217 scope(exit) () @trusted { rmdirRecurse(tmpFolder ~ "/bug15771"); }(); 218 219 f.log("Hello World!"); 220 assert(exists(filepath)); 221 f.file.close(); 222 } 223 224 @system unittest 225 { 226 import std.array : empty; 227 import std.file : deleteme, remove; 228 import std.string : indexOf; 229 230 string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 231 auto file = File(filename, "w"); 232 auto l = new FileLogger(file); 233 scope(exit)234 scope(exit) 235 { 236 remove(filename); 237 } 238 239 string notWritten = "this should not be written to file"; 240 string written = "this should be written to file"; 241 242 l.logLevel = LogLevel.critical; 243 l.log(LogLevel.warning, notWritten); 244 l.log(LogLevel.critical, written); 245 file.close(); 246 247 file = File(filename, "r"); 248 string readLine = file.readln(); 249 assert(readLine.indexOf(written) != -1, readLine); 250 readLine = file.readln(); 251 assert(readLine.indexOf(notWritten) == -1, readLine); 252 file.close(); 253 } 254 255 @safe unittest 256 { 257 auto dl = cast(FileLogger) sharedLog; 258 assert(dl !is null); 259 assert(dl.logLevel == LogLevel.all); 260 assert(globalLogLevel == LogLevel.all); 261 262 auto tl = cast(StdForwardLogger) stdThreadLocalLog; 263 assert(tl !is null); 264 stdThreadLocalLog.logLevel = LogLevel.all; 265 } 266