]> git.ipfire.org Git - thirdparty/gcc.git/blob - libphobos/src/std/experimental/logger/filelogger.d
Add D front-end, libphobos library, and D2 testsuite.
[thirdparty/gcc.git] / libphobos / src / std / experimental / logger / filelogger.d
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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
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);
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
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 }