mirror of
https://github.com/dlang/phobos.git
synced 2025-05-02 08:00:48 +03:00

Abstract methods have to be marked 'abstract', or the compiler wont complain about missing implementations, but instead assume they will be in some .o file that is later passed to the linker. Made reads/writes to globalLogLevel atomic and separated global log level from stdlog's log level. (Had to add a cast to the first array element of LogLevel arrays in foreach(). Possibly a compiler bug in 2.066 master.) Avoid races with reading/writing the standard Logger: * User code can no longer directly read out the stdlog, because by the time it has the reference, it might already be obsolete. (Later an `opApply` style function could be provided to execute functions/delegates in the context of a locked stdlog.) * The stdlog property was split up into a public setter `stdlog` and a package getter `stdlogImpl`, because the compiler doesn't like different visibility for getter and setter. * All module level functions dealing with the stdlog now synchronize with the global `__stdloggermutex`. static assert(__ctfe) doesn't do what one might expect. Code is always generated and emitted for `isLoggingActive` and it can be called at runtime. This commit turns the template function into just a template `isLoggingActiveAt` and a global bool flag `isLoggingActive`, so they can both be evaluated without parenthesis now and don't end up as functions in the final executable. Fix: Unittesting symbols like randomString() are visible outside of the logger package. Changed Logger methods to `final`, that are not documented as overridable to gain more control over the implementation details when I implement thread safety measures. Made `fatalHandler` accessible as a property to allow synchronization in later commits. Also made `writeLogMsg` protected, so it can assume it is properly synchronized already by the public calls calling it. To log a pre-built message `forwardMsg` can now be used. (See `FileLogger`). Fixed my mistake, where setting `stdlog` before querying `stdlogImpl` would revert the setting. And while I was at it, I changed `stdlog`s behavoir on assignment of `null`: It will now put the default logger back in place. First attempt on making logging thread safe: Logger now has a mutex to protect its public API. The module level functions don't perform any runtime checks themselves to avoid races. Instead the logger does all checking under protection of its mutex. `globalLogLevel` will be evaluated only once for any public logging call and thus needs no global locking. All overrides of `writeLogMsg`, `beginLogMsg`, `logMsgPart` and `finishLogMsg` have `protected` visibility now, since they expect their logger to be already under mutex protection and must not be publically visible. Fixed `MultiLogger` to not perform checks for its children to avoid races. Small fix for `StdLoggerDisableLogging`. some more unittests and some fixes doc fixes doc fix MrSmith33 Array problem stupid me stupid me The getter for Logger.logLevel now uses an `atomicLoad` instead of full mutex lock. Read-access to `stdlog` is now back... Fixed inverted DDoc meaning for `isLoggingActive`. Removed synchronization with a global lock for the duration of any standard logging call in an exchange of sequential consinstency for throughput. Rejects valid: logf(LogLevel.fatal, "I am %s", true); (The template constraint is not required in this case.) Fix for `template isLoggingActive` that would break if an actual log level was disabled.
192 lines
5.7 KiB
D
192 lines
5.7 KiB
D
module std.experimental.logger.filelogger;
|
|
|
|
import std.stdio;
|
|
import std.string;
|
|
import std.datetime : SysTime;
|
|
import std.concurrency;
|
|
import std.experimental.logger.core;
|
|
|
|
import core.sync.mutex;
|
|
|
|
/** 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.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
|
|
$(D LogLevel) for $(D FileLogger) is $(D LogLevel.info).
|
|
|
|
Example:
|
|
-------------
|
|
auto l1 = new FileLogger("logFile", "loggerName");
|
|
auto l2 = new FileLogger("logFile", "loggerName", LogLevel.fatal);
|
|
-------------
|
|
*/
|
|
@trusted this(in string fn, const LogLevel lv = LogLevel.info)
|
|
{
|
|
import std.exception : enforce;
|
|
super(lv);
|
|
this.filename = fn;
|
|
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.info).
|
|
|
|
Example:
|
|
-------------
|
|
auto file = File("logFile.log", "w");
|
|
auto l1 = new FileLogger(&file, "LoggerName");
|
|
auto l2 = new FileLogger(&file, "LoggerName", LogLevel.fatal);
|
|
-------------
|
|
*/
|
|
this(File file, const LogLevel lv = LogLevel.info)
|
|
{
|
|
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. Otherwise a default
|
|
initialized $(D File) reference will be returned.
|
|
*/
|
|
@property File file()
|
|
{
|
|
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)
|
|
@trusted
|
|
{
|
|
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();
|
|
}
|
|
|
|
private File file_;
|
|
private string filename;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.file : remove;
|
|
import std.array : empty;
|
|
import std.string : indexOf;
|
|
|
|
string filename = randomString(32) ~ ".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);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.file : remove;
|
|
import std.array : empty;
|
|
import std.string : indexOf;
|
|
|
|
string filename = randomString(32) ~ ".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();
|
|
}
|
|
|
|
unittest
|
|
{
|
|
auto dl = stdlog;
|
|
assert(dl !is null);
|
|
assert(dl.logLevel == LogLevel.all);
|
|
assert(globalLogLevel == LogLevel.all);
|
|
}
|