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