1*b1e83836Smrg // Written in the D programming language. 2*b1e83836Smrg /** 3*b1e83836Smrg Source: $(PHOBOSSRC std/experimental/logger/filelogger.d) 4*b1e83836Smrg */ 5181254a7Smrg module std.experimental.logger.filelogger; 6181254a7Smrg 7181254a7Smrg import std.experimental.logger.core; 8181254a7Smrg import std.stdio; 9181254a7Smrg 10181254a7Smrg import std.typecons : Flag; 11181254a7Smrg 12181254a7Smrg /** An option to create $(LREF FileLogger) directory if it is non-existent. 13181254a7Smrg */ 14181254a7Smrg alias CreateFolder = Flag!"CreateFolder"; 15181254a7Smrg 16*b1e83836Smrg /** This `Logger` implementation writes log messages to the associated 17181254a7Smrg file. The name of the file has to be passed on construction time. If the file 18181254a7Smrg is already present new log messages will be append at its end. 19181254a7Smrg */ 20181254a7Smrg class FileLogger : Logger 21181254a7Smrg { 22181254a7Smrg import std.concurrency : Tid; 23181254a7Smrg import std.datetime.systime : SysTime; 24*b1e83836Smrg import std.format.write : formattedWrite; 25181254a7Smrg 26*b1e83836Smrg /** A constructor for the `FileLogger` Logger. 27181254a7Smrg 28181254a7Smrg Params: 29*b1e83836Smrg fn = The filename of the output file of the `FileLogger`. If that 30181254a7Smrg file can not be opened for writting an exception will be thrown. 31*b1e83836Smrg lv = The `LogLevel` for the `FileLogger`. By default the 32181254a7Smrg 33181254a7Smrg Example: 34181254a7Smrg ------------- 35181254a7Smrg auto l1 = new FileLogger("logFile"); 36181254a7Smrg auto l2 = new FileLogger("logFile", LogLevel.fatal); 37181254a7Smrg auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes); 38181254a7Smrg ------------- 39181254a7Smrg */ 40*b1e83836Smrg this(const string fn, const LogLevel lv = LogLevel.all) @safe 41181254a7Smrg { 42181254a7Smrg this(fn, lv, CreateFolder.yes); 43181254a7Smrg } 44181254a7Smrg 45*b1e83836Smrg /** A constructor for the `FileLogger` Logger that takes a reference to 46*b1e83836Smrg a `File`. 47181254a7Smrg 48*b1e83836Smrg The `File` passed must be open for all the log call to the 49*b1e83836Smrg `FileLogger`. If the `File` gets closed, using the `FileLogger` 50181254a7Smrg for logging will result in undefined behaviour. 51181254a7Smrg 52181254a7Smrg Params: 53181254a7Smrg fn = The file used for logging. 54*b1e83836Smrg lv = The `LogLevel` for the `FileLogger`. By default the 55*b1e83836Smrg `LogLevel` for `FileLogger` is `LogLevel.all`. 56181254a7Smrg createFileNameFolder = if yes and fn contains a folder name, this 57181254a7Smrg folder will be created. 58181254a7Smrg 59181254a7Smrg Example: 60181254a7Smrg ------------- 61181254a7Smrg auto file = File("logFile.log", "w"); 62181254a7Smrg auto l1 = new FileLogger(file); 63181254a7Smrg auto l2 = new FileLogger(file, LogLevel.fatal); 64181254a7Smrg ------------- 65181254a7Smrg */ this(const string fn,const LogLevel lv,CreateFolder createFileNameFolder)66*b1e83836Smrg this(const string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe 67181254a7Smrg { 68181254a7Smrg import std.file : exists, mkdirRecurse; 69181254a7Smrg import std.path : dirName; 70181254a7Smrg import std.conv : text; 71181254a7Smrg 72181254a7Smrg super(lv); 73181254a7Smrg this.filename = fn; 74181254a7Smrg 75181254a7Smrg if (createFileNameFolder) 76181254a7Smrg { 77181254a7Smrg auto d = dirName(this.filename); 78181254a7Smrg mkdirRecurse(d); 79181254a7Smrg assert(exists(d), text("The folder the FileLogger should have", 80181254a7Smrg " created in '", d,"' could not be created.")); 81181254a7Smrg } 82181254a7Smrg 83181254a7Smrg this.file_.open(this.filename, "a"); 84181254a7Smrg } 85181254a7Smrg 86*b1e83836Smrg /** A constructor for the `FileLogger` Logger that takes a reference to 87*b1e83836Smrg a `File`. 88181254a7Smrg 89*b1e83836Smrg The `File` passed must be open for all the log call to the 90*b1e83836Smrg `FileLogger`. If the `File` gets closed, using the `FileLogger` 91181254a7Smrg for logging will result in undefined behaviour. 92181254a7Smrg 93181254a7Smrg Params: 94181254a7Smrg file = The file used for logging. 95*b1e83836Smrg lv = The `LogLevel` for the `FileLogger`. By default the 96*b1e83836Smrg `LogLevel` for `FileLogger` is `LogLevel.all`. 97181254a7Smrg 98181254a7Smrg Example: 99181254a7Smrg ------------- 100181254a7Smrg auto file = File("logFile.log", "w"); 101181254a7Smrg auto l1 = new FileLogger(file); 102181254a7Smrg auto l2 = new FileLogger(file, LogLevel.fatal); 103181254a7Smrg ------------- 104181254a7Smrg */ 105181254a7Smrg this(File file, const LogLevel lv = LogLevel.all) @safe 106181254a7Smrg { 107181254a7Smrg super(lv); 108181254a7Smrg this.file_ = file; 109181254a7Smrg } 110181254a7Smrg 111*b1e83836Smrg /** If the `FileLogger` is managing the `File` it logs to, this 112181254a7Smrg method will return a reference to this File. 113181254a7Smrg */ file()114181254a7Smrg @property File file() @safe 115181254a7Smrg { 116181254a7Smrg return this.file_; 117181254a7Smrg } 118181254a7Smrg 119181254a7Smrg /* This method overrides the base class method in order to log to a file 120*b1e83836Smrg without requiring heap allocated memory. Additionally, the `FileLogger` 121181254a7Smrg local mutex is logged to serialize the log calls. 122181254a7Smrg */ beginLogMsg(string file,int line,string funcName,string prettyFuncName,string moduleName,LogLevel logLevel,Tid threadId,SysTime timestamp,Logger logger)123181254a7Smrg override protected void beginLogMsg(string file, int line, string funcName, 124181254a7Smrg string prettyFuncName, string moduleName, LogLevel logLevel, 125181254a7Smrg Tid threadId, SysTime timestamp, Logger logger) 126181254a7Smrg @safe 127181254a7Smrg { 128181254a7Smrg import std.string : lastIndexOf; 129181254a7Smrg ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; 130181254a7Smrg ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; 131181254a7Smrg 132181254a7Smrg auto lt = this.file_.lockingTextWriter(); 133181254a7Smrg systimeToISOString(lt, timestamp); 134*b1e83836Smrg import std.conv : to; 135*b1e83836Smrg formattedWrite(lt, " [%s] %s:%u:%s ", logLevel.to!string, 136*b1e83836Smrg file[fnIdx .. $], line, funcName[funIdx .. $]); 137181254a7Smrg } 138181254a7Smrg 139181254a7Smrg /* This methods overrides the base class method and writes the parts of 140181254a7Smrg the log call directly to the file. 141181254a7Smrg */ logMsgPart(scope const (char)[]msg)142*b1e83836Smrg override protected void logMsgPart(scope const(char)[] msg) 143181254a7Smrg { 144181254a7Smrg formattedWrite(this.file_.lockingTextWriter(), "%s", msg); 145181254a7Smrg } 146181254a7Smrg 147181254a7Smrg /* This methods overrides the base class method and finalizes the active 148*b1e83836Smrg log call. This requires flushing the `File` and releasing the 149*b1e83836Smrg `FileLogger` local mutex. 150181254a7Smrg */ finishLogMsg()151181254a7Smrg override protected void finishLogMsg() 152181254a7Smrg { 153181254a7Smrg this.file_.lockingTextWriter().put("\n"); 154181254a7Smrg this.file_.flush(); 155181254a7Smrg } 156181254a7Smrg 157181254a7Smrg /* This methods overrides the base class method and delegates the 158*b1e83836Smrg `LogEntry` data to the actual implementation. 159181254a7Smrg */ writeLogMsg(ref LogEntry payload)160181254a7Smrg override protected void writeLogMsg(ref LogEntry payload) 161181254a7Smrg { 162181254a7Smrg this.beginLogMsg(payload.file, payload.line, payload.funcName, 163181254a7Smrg payload.prettyFuncName, payload.moduleName, payload.logLevel, 164181254a7Smrg payload.threadId, payload.timestamp, payload.logger); 165181254a7Smrg this.logMsgPart(payload.msg); 166181254a7Smrg this.finishLogMsg(); 167181254a7Smrg } 168181254a7Smrg 169*b1e83836Smrg /** If the `FileLogger` was constructed with a filename, this method 170*b1e83836Smrg returns this filename. Otherwise an empty `string` is returned. 171181254a7Smrg */ getFilename()172181254a7Smrg string getFilename() 173181254a7Smrg { 174181254a7Smrg return this.filename; 175181254a7Smrg } 176181254a7Smrg 177*b1e83836Smrg /** The `File` log messages are written to. */ 178*b1e83836Smrg protected File file_; 179*b1e83836Smrg 180*b1e83836Smrg /** The filename of the `File` log messages are written to. */ 181*b1e83836Smrg protected string filename; 182181254a7Smrg } 183181254a7Smrg 184181254a7Smrg @system unittest 185181254a7Smrg { 186181254a7Smrg import std.array : empty; 187181254a7Smrg import std.file : deleteme, remove; 188181254a7Smrg import std.string : indexOf; 189181254a7Smrg 190181254a7Smrg string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 191181254a7Smrg auto l = new FileLogger(filename); 192181254a7Smrg scope(exit)193181254a7Smrg scope(exit) 194181254a7Smrg { 195181254a7Smrg remove(filename); 196181254a7Smrg } 197181254a7Smrg 198181254a7Smrg string notWritten = "this should not be written to file"; 199181254a7Smrg string written = "this should be written to file"; 200181254a7Smrg 201181254a7Smrg l.logLevel = LogLevel.critical; 202181254a7Smrg l.log(LogLevel.warning, notWritten); 203181254a7Smrg l.log(LogLevel.critical, written); 204181254a7Smrg destroy(l); 205181254a7Smrg 206181254a7Smrg auto file = File(filename, "r"); 207181254a7Smrg string readLine = file.readln(); 208181254a7Smrg assert(readLine.indexOf(written) != -1, readLine); 209181254a7Smrg readLine = file.readln(); 210181254a7Smrg assert(readLine.indexOf(notWritten) == -1, readLine); 211181254a7Smrg } 212181254a7Smrg 213181254a7Smrg @safe unittest 214181254a7Smrg { 215181254a7Smrg import std.file : rmdirRecurse, exists, deleteme; 216181254a7Smrg import std.path : dirName; 217181254a7Smrg 218181254a7Smrg const string tmpFolder = dirName(deleteme); 219181254a7Smrg const string filepath = tmpFolder ~ "/bug15771/minas/oops/"; 220181254a7Smrg const string filename = filepath ~ "output.txt"; 221181254a7Smrg assert(!exists(filepath)); 222181254a7Smrg 223181254a7Smrg auto f = new FileLogger(filename, LogLevel.all, CreateFolder.yes); scope(exit)224181254a7Smrg scope(exit) () @trusted { rmdirRecurse(tmpFolder ~ "/bug15771"); }(); 225181254a7Smrg 226181254a7Smrg f.log("Hello World!"); 227181254a7Smrg assert(exists(filepath)); 228181254a7Smrg f.file.close(); 229181254a7Smrg } 230181254a7Smrg 231181254a7Smrg @system unittest 232181254a7Smrg { 233181254a7Smrg import std.array : empty; 234181254a7Smrg import std.file : deleteme, remove; 235181254a7Smrg import std.string : indexOf; 236181254a7Smrg 237181254a7Smrg string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile"; 238181254a7Smrg auto file = File(filename, "w"); 239181254a7Smrg auto l = new FileLogger(file); 240181254a7Smrg scope(exit)241181254a7Smrg scope(exit) 242181254a7Smrg { 243181254a7Smrg remove(filename); 244181254a7Smrg } 245181254a7Smrg 246181254a7Smrg string notWritten = "this should not be written to file"; 247181254a7Smrg string written = "this should be written to file"; 248181254a7Smrg 249181254a7Smrg l.logLevel = LogLevel.critical; 250181254a7Smrg l.log(LogLevel.warning, notWritten); 251181254a7Smrg l.log(LogLevel.critical, written); 252181254a7Smrg file.close(); 253181254a7Smrg 254181254a7Smrg file = File(filename, "r"); 255181254a7Smrg string readLine = file.readln(); 256181254a7Smrg assert(readLine.indexOf(written) != -1, readLine); 257181254a7Smrg readLine = file.readln(); 258181254a7Smrg assert(readLine.indexOf(notWritten) == -1, readLine); 259181254a7Smrg file.close(); 260181254a7Smrg } 261181254a7Smrg 262181254a7Smrg @safe unittest 263181254a7Smrg { 264181254a7Smrg auto dl = cast(FileLogger) sharedLog; 265181254a7Smrg assert(dl !is null); 266*b1e83836Smrg assert(dl.logLevel == LogLevel.info); 267181254a7Smrg assert(globalLogLevel == LogLevel.all); 268181254a7Smrg 269181254a7Smrg auto tl = cast(StdForwardLogger) stdThreadLocalLog; 270181254a7Smrg assert(tl !is null); 271181254a7Smrg stdThreadLocalLog.logLevel = LogLevel.all; 272181254a7Smrg } 273