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(this This)(const string fn, const LogLevel lv = LogLevel.all) 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(this This)(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) 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 // Cast away `shared` when the constructor is inferred shared. 84 () @trusted { (cast() this.file_).open(this.filename, "a"); }(); 85 } 86 87 /** A constructor for the `FileLogger` Logger that takes a reference to 88 a `File`. 89 90 The `File` passed must be open for all the log call to the 91 `FileLogger`. If the `File` gets closed, using the `FileLogger` 92 for logging will result in undefined behaviour. 93 94 Params: 95 file = The file used for logging. 96 lv = The `LogLevel` for the `FileLogger`. By default the 97 `LogLevel` for `FileLogger` is `LogLevel.all`. 98 99 Example: 100 ------------- 101 auto file = File("logFile.log", "w"); 102 auto l1 = new FileLogger(file); 103 auto l2 = new FileLogger(file, LogLevel.fatal); 104 ------------- 105 */ 106 this(File file, const LogLevel lv = LogLevel.all) @safe 107 { 108 super(lv); 109 this.file_ = file; 110 } 111 112 /** If the `FileLogger` is managing the `File` it logs to, this 113 method will return a reference to this File. 114 */ 115 @property File file() @safe 116 { 117 return this.file_; 118 } 119 120 /* This method overrides the base class method in order to log to a file 121 without requiring heap allocated memory. Additionally, the `FileLogger` 122 local mutex is logged to serialize the log calls. 123 */ 124 override protected void beginLogMsg(string file, int line, string funcName, 125 string prettyFuncName, string moduleName, LogLevel logLevel, 126 Tid threadId, SysTime timestamp, Logger logger) 127 @safe 128 { 129 import std.string : lastIndexOf; 130 ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; 131 ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; 132 133 auto lt = this.file_.lockingTextWriter(); 134 systimeToISOString(lt, timestamp); 135 import std.conv : to; 136 formattedWrite(lt, " [%s] %s:%u:%s ", logLevel.to!string, 137 file[fnIdx .. $], line, funcName[funIdx .. $]); 138 } 139 140 /* This methods overrides the base class method and writes the parts of 141 the log call directly to the file. 142 */ 143 override protected void logMsgPart(scope const(char)[] msg) 144 { 145 formattedWrite(this.file_.lockingTextWriter(), "%s", msg); 146 } 147 148 /* This methods overrides the base class method and finalizes the active 149 log call. This requires flushing the `File` and releasing the 150 `FileLogger` local mutex. 151 */ 152 override protected void finishLogMsg() 153 { 154 this.file_.lockingTextWriter().put("\n"); 155 this.file_.flush(); 156 } 157 158 /* This methods overrides the base class method and delegates the 159 `LogEntry` data to the actual implementation. 160 */ 161 override protected void writeLogMsg(ref LogEntry payload) 162 { 163 this.beginLogMsg(payload.file, payload.line, payload.funcName, 164 payload.prettyFuncName, payload.moduleName, payload.logLevel, 165 payload.threadId, payload.timestamp, payload.logger); 166 this.logMsgPart(payload.msg); 167 this.finishLogMsg(); 168 } 169 170 /** If the `FileLogger` was constructed with a filename, this method 171 returns this filename. Otherwise an empty `string` is returned. 172 */ 173 string getFilename() 174 { 175 return this.filename; 176 } 177 178 /** The `File` log messages are written to. */ 179 protected File file_; 180 181 /** The filename of the `File` log messages are written to. */ 182 protected string filename; 183 } 184 185 @system unittest 186 { 187 import std.array : empty; 188 import std.file : deleteme, remove; 189 import std.string : indexOf; 190 191 string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 192 auto l = new FileLogger(filename); 193 194 scope(exit) 195 { 196 remove(filename); 197 } 198 199 string notWritten = "this should not be written to file"; 200 string written = "this should be written to file"; 201 202 l.logLevel = LogLevel.critical; 203 l.log(LogLevel.warning, notWritten); 204 l.log(LogLevel.critical, written); 205 destroy(l); 206 207 auto file = File(filename, "r"); 208 string readLine = file.readln(); 209 assert(readLine.indexOf(written) != -1, readLine); 210 readLine = file.readln(); 211 assert(readLine.indexOf(notWritten) == -1, readLine); 212 } 213 214 @safe unittest 215 { 216 import std.file : rmdirRecurse, exists, deleteme; 217 import std.path : dirName; 218 219 const string tmpFolder = dirName(deleteme); 220 const string filepath = tmpFolder ~ "/bug15771/minas/oops/"; 221 const string filename = filepath ~ "output.txt"; 222 assert(!exists(filepath)); 223 224 auto f = new FileLogger(filename, LogLevel.all, CreateFolder.yes); 225 scope(exit) () @trusted { rmdirRecurse(tmpFolder ~ "/bug15771"); }(); 226 227 f.log("Hello World!"); 228 assert(exists(filepath)); 229 f.file.close(); 230 } 231 232 @system unittest 233 { 234 import std.array : empty; 235 import std.file : deleteme, remove; 236 import std.string : indexOf; 237 238 string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 239 auto file = File(filename, "w"); 240 auto l = new FileLogger(file); 241 242 scope(exit) 243 { 244 remove(filename); 245 } 246 247 string notWritten = "this should not be written to file"; 248 string written = "this should be written to file"; 249 250 l.logLevel = LogLevel.critical; 251 l.log(LogLevel.warning, notWritten); 252 l.log(LogLevel.critical, written); 253 file.close(); 254 255 file = File(filename, "r"); 256 string readLine = file.readln(); 257 assert(readLine.indexOf(written) != -1, readLine); 258 readLine = file.readln(); 259 assert(readLine.indexOf(notWritten) == -1, readLine); 260 file.close(); 261 } 262 263 @system unittest 264 { 265 auto dl = cast(FileLogger) sharedLog; 266 assert(dl !is null); 267 assert(dl.logLevel == LogLevel.info); 268 assert(globalLogLevel == LogLevel.all); 269 270 auto tl = cast(StdForwardLogger) stdThreadLocalLog; 271 assert(tl !is null); 272 stdThreadLocalLog.logLevel = LogLevel.all; 273 } 274 275 @safe unittest 276 { 277 // we don't need to actually run the code, only make sure 278 // it compiles 279 static void _() { 280 auto l = new shared FileLogger(""); 281 } 282 }