mirror of
https://github.com/dlang/phobos.git
synced 2025-04-30 15:10:46 +03:00
265 lines
7.9 KiB
D
265 lines
7.9 KiB
D
///
|
|
module std.experimental.logger.filelogger;
|
|
|
|
import std.experimental.logger.core;
|
|
import std.stdio;
|
|
|
|
import std.typecons : Flag;
|
|
|
|
/** An option to create $(LREF FileLogger) directory if it is non-existent.
|
|
*/
|
|
alias CreateFolder = Flag!"CreateFolder";
|
|
|
|
/** This $(D Logger) implementation writes log messages to the associated
|
|
file. The name of the file has to be passed on construction time. If the file
|
|
is already present new log messages will be append at its end.
|
|
*/
|
|
class FileLogger : Logger
|
|
{
|
|
import std.concurrency : Tid;
|
|
import std.datetime.systime : SysTime;
|
|
import std.format : formattedWrite;
|
|
|
|
/** A constructor for the $(D FileLogger) Logger.
|
|
|
|
Params:
|
|
fn = The filename of the output file of the $(D FileLogger). If that
|
|
file can not be opened for writting an exception will be thrown.
|
|
lv = The $(D LogLevel) for the $(D FileLogger). By default the
|
|
|
|
Example:
|
|
-------------
|
|
auto l1 = new FileLogger("logFile");
|
|
auto l2 = new FileLogger("logFile", LogLevel.fatal);
|
|
auto l3 = new FileLogger("logFile", LogLevel.fatal, CreateFolder.yes);
|
|
-------------
|
|
*/
|
|
this(in string fn, const LogLevel lv = LogLevel.all) @safe
|
|
{
|
|
this(fn, lv, CreateFolder.yes);
|
|
}
|
|
|
|
/** A constructor for the $(D FileLogger) Logger that takes a reference to
|
|
a $(D File).
|
|
|
|
The $(D File) passed must be open for all the log call to the
|
|
$(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger)
|
|
for logging will result in undefined behaviour.
|
|
|
|
Params:
|
|
fn = The file used for logging.
|
|
lv = The $(D LogLevel) for the $(D FileLogger). By default the
|
|
$(D LogLevel) for $(D FileLogger) is $(D LogLevel.all).
|
|
createFileNameFolder = if yes and fn contains a folder name, this
|
|
folder will be created.
|
|
|
|
Example:
|
|
-------------
|
|
auto file = File("logFile.log", "w");
|
|
auto l1 = new FileLogger(file);
|
|
auto l2 = new FileLogger(file, LogLevel.fatal);
|
|
-------------
|
|
*/
|
|
this(in string fn, const LogLevel lv, CreateFolder createFileNameFolder) @safe
|
|
{
|
|
import std.file : exists, mkdirRecurse;
|
|
import std.path : dirName;
|
|
import std.conv : text;
|
|
|
|
super(lv);
|
|
this.filename = fn;
|
|
|
|
if (createFileNameFolder)
|
|
{
|
|
auto d = dirName(this.filename);
|
|
mkdirRecurse(d);
|
|
assert(exists(d), text("The folder the FileLogger should have",
|
|
" created in '", d,"' could not be created."));
|
|
}
|
|
|
|
this.file_.open(this.filename, "a");
|
|
}
|
|
|
|
/** A constructor for the $(D FileLogger) Logger that takes a reference to
|
|
a $(D File).
|
|
|
|
The $(D File) passed must be open for all the log call to the
|
|
$(D FileLogger). If the $(D File) gets closed, using the $(D FileLogger)
|
|
for logging will result in undefined behaviour.
|
|
|
|
Params:
|
|
file = The file used for logging.
|
|
lv = The $(D LogLevel) for the $(D FileLogger). By default the
|
|
$(D LogLevel) for $(D FileLogger) is $(D LogLevel.all).
|
|
|
|
Example:
|
|
-------------
|
|
auto file = File("logFile.log", "w");
|
|
auto l1 = new FileLogger(file);
|
|
auto l2 = new FileLogger(file, LogLevel.fatal);
|
|
-------------
|
|
*/
|
|
this(File file, const LogLevel lv = LogLevel.all) @safe
|
|
{
|
|
super(lv);
|
|
this.file_ = file;
|
|
}
|
|
|
|
/** If the $(D FileLogger) is managing the $(D File) it logs to, this
|
|
method will return a reference to this File.
|
|
*/
|
|
@property File file() @safe
|
|
{
|
|
return this.file_;
|
|
}
|
|
|
|
/* This method overrides the base class method in order to log to a file
|
|
without requiring heap allocated memory. Additionally, the $(D FileLogger)
|
|
local mutex is logged to serialize the log calls.
|
|
*/
|
|
override protected void beginLogMsg(string file, int line, string funcName,
|
|
string prettyFuncName, string moduleName, LogLevel logLevel,
|
|
Tid threadId, SysTime timestamp, Logger logger)
|
|
@safe
|
|
{
|
|
import std.string : lastIndexOf;
|
|
ptrdiff_t fnIdx = file.lastIndexOf('/') + 1;
|
|
ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1;
|
|
|
|
auto lt = this.file_.lockingTextWriter();
|
|
systimeToISOString(lt, timestamp);
|
|
formattedWrite(lt, ":%s:%s:%u ", file[fnIdx .. $],
|
|
funcName[funIdx .. $], line);
|
|
}
|
|
|
|
/* This methods overrides the base class method and writes the parts of
|
|
the log call directly to the file.
|
|
*/
|
|
override protected void logMsgPart(const(char)[] msg)
|
|
{
|
|
formattedWrite(this.file_.lockingTextWriter(), "%s", msg);
|
|
}
|
|
|
|
/* This methods overrides the base class method and finalizes the active
|
|
log call. This requires flushing the $(D File) and releasing the
|
|
$(D FileLogger) local mutex.
|
|
*/
|
|
override protected void finishLogMsg()
|
|
{
|
|
this.file_.lockingTextWriter().put("\n");
|
|
this.file_.flush();
|
|
}
|
|
|
|
/* This methods overrides the base class method and delegates the
|
|
$(D LogEntry) data to the actual implementation.
|
|
*/
|
|
override protected void writeLogMsg(ref LogEntry payload)
|
|
{
|
|
this.beginLogMsg(payload.file, payload.line, payload.funcName,
|
|
payload.prettyFuncName, payload.moduleName, payload.logLevel,
|
|
payload.threadId, payload.timestamp, payload.logger);
|
|
this.logMsgPart(payload.msg);
|
|
this.finishLogMsg();
|
|
}
|
|
|
|
/** If the $(D FileLogger) was constructed with a filename, this method
|
|
returns this filename. Otherwise an empty $(D string) is returned.
|
|
*/
|
|
string getFilename()
|
|
{
|
|
return this.filename;
|
|
}
|
|
|
|
private File file_;
|
|
private string filename;
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.array : empty;
|
|
import std.file : deleteme, remove;
|
|
import std.string : indexOf;
|
|
|
|
string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
|
|
auto l = new FileLogger(filename);
|
|
|
|
scope(exit)
|
|
{
|
|
remove(filename);
|
|
}
|
|
|
|
string notWritten = "this should not be written to file";
|
|
string written = "this should be written to file";
|
|
|
|
l.logLevel = LogLevel.critical;
|
|
l.log(LogLevel.warning, notWritten);
|
|
l.log(LogLevel.critical, written);
|
|
destroy(l);
|
|
|
|
auto file = File(filename, "r");
|
|
string readLine = file.readln();
|
|
assert(readLine.indexOf(written) != -1, readLine);
|
|
readLine = file.readln();
|
|
assert(readLine.indexOf(notWritten) == -1, readLine);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.file : rmdirRecurse, exists, deleteme;
|
|
import std.path : dirName;
|
|
|
|
const string tmpFolder = dirName(deleteme);
|
|
const string filepath = tmpFolder ~ "/bug15771/minas/oops/";
|
|
const string filename = filepath ~ "output.txt";
|
|
assert(!exists(filepath));
|
|
|
|
auto f = new FileLogger(filename, LogLevel.all, CreateFolder.yes);
|
|
scope(exit) () @trusted { rmdirRecurse(tmpFolder ~ "/bug15771"); }();
|
|
|
|
f.log("Hello World!");
|
|
assert(exists(filepath));
|
|
f.file.close();
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.array : empty;
|
|
import std.file : deleteme, remove;
|
|
import std.string : indexOf;
|
|
|
|
string filename = deleteme ~ __FUNCTION__ ~ ".tempLogFile";
|
|
auto file = File(filename, "w");
|
|
auto l = new FileLogger(file);
|
|
|
|
scope(exit)
|
|
{
|
|
remove(filename);
|
|
}
|
|
|
|
string notWritten = "this should not be written to file";
|
|
string written = "this should be written to file";
|
|
|
|
l.logLevel = LogLevel.critical;
|
|
l.log(LogLevel.warning, notWritten);
|
|
l.log(LogLevel.critical, written);
|
|
file.close();
|
|
|
|
file = File(filename, "r");
|
|
string readLine = file.readln();
|
|
assert(readLine.indexOf(written) != -1, readLine);
|
|
readLine = file.readln();
|
|
assert(readLine.indexOf(notWritten) == -1, readLine);
|
|
file.close();
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
auto dl = cast(FileLogger) sharedLog;
|
|
assert(dl !is null);
|
|
assert(dl.logLevel == LogLevel.all);
|
|
assert(globalLogLevel == LogLevel.all);
|
|
|
|
auto tl = cast(StdForwardLogger) stdThreadLocalLog;
|
|
assert(tl !is null);
|
|
stdThreadLocalLog.logLevel = LogLevel.all;
|
|
}
|