xref: /netbsd-src/external/gpl3/gcc.old/dist/libphobos/src/std/experimental/logger/filelogger.d (revision 627f7eb200a4419d89b531d55fccd2ee3ffdcde0)
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     */
this(in string fn,const LogLevel lv,CreateFolder createFileNameFolder)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     */
file()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     */
beginLogMsg(string file,int line,string funcName,string prettyFuncName,string moduleName,LogLevel logLevel,Tid threadId,SysTime timestamp,Logger logger)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     */
logMsgPart(const (char)[]msg)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     */
finishLogMsg()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     */
writeLogMsg(ref LogEntry payload)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     */
getFilename()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 
scope(exit)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);
scope(exit)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 
scope(exit)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 }
266