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 }