xref: /netbsd-src/external/gpl3/gcc/dist/libphobos/src/std/experimental/logger/filelogger.d (revision b1e838363e3c6fc78a55519254d99869742dd33c)
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