Merge pull request #5 from deavmi/feature/nextgen

DLog v2 - Complete re-write
This commit is contained in:
Tristan B. Velloza Kildaire 2024-04-10 13:55:21 +02:00 committed by GitHub
commit 55de4b727d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 752 additions and 770 deletions

129
README.md
View File

@ -37,9 +37,15 @@ dlog is formed out of two main components:
1. `Logger`
* The logger contains the needed barebones for facilitating the actual logging of text
2. `MessageTransform`
* A MessageTransform is attached to a logger and performs manipulation on the text input into the logger for logging
* The _base logger_ (i.e. `Logger`) maintains a list of attaches _filters_, _message transformers_ and _handlers_
2. `Filter`
* Acts as a predicate on the incoming _message_ and determines whether it should be logged or not
* This is used by the `BasicLogger` to implement log levels
3. `Transform`
* A _message transform_ is attached to a logger and performs manipulation on the message logged
* They may be chained as to perform multiple transformations in a stream-like fashion
4. `Handler`
* A _handler_ handles the final transformed message, for some this means outputting to standard out, or a file
### Quick start
@ -49,60 +55,29 @@ simply use the default logger as follows:
```d
import dlog;
Logger logger = new DefaultLogger();
DefaultLogger logger = new DefaultLogger();
logger.setLevel(Level.DEBUG);
logger.error(["woah", "LEVELS!"], 69.420);
logger.info(["woah", "LEVELS!"], 69.420);
logger.warn(["woah", "LEVELS!"], 69.420);
logger.debug_(["woah", "LEVELS!"], 69.420);
logger.log("This is a log message");
logger.log(1);
logger.log(1==1);
logger.log([1,2,3]);
// Should not be able to see this
logger.setLevel(Level.INFO);
logger.debug_("Can't see me!");
```
This will output the following:
```
[2021-Dec-23 11:17:35.3527637] (source/dlog/testing/thing.d:12): This is a log message
[2021-Dec-23 11:17:35.3527717] (source/dlog/testing/thing.d:13): 1
[2021-Dec-23 11:17:35.3527789] (source/dlog/testing/thing.d:14): true
[2021-Dec-23 11:17:35.3527871] (source/dlog/testing/thing.d:15): [1, 2, 3]
[2024-Apr-09 19:14:38.3077171] (ERROR): ["woah", "LEVELS!"] 69.42
[2024-Apr-09 19:14:38.3077346] (INFO): ["woah", "LEVELS!"] 69.42
[2024-Apr-09 19:14:38.3077559] (WARN): ["woah", "LEVELS!"] 69.42
[2024-Apr-09 19:14:38.3077759] (DEBUG): ["woah", "LEVELS!"] 69.42
```
As you can see file and line numbering of where the `log()` function is called appears in the log message which can be quite helpful
for debugging.
---
We also support many different logging levels which can be accomplished using the `error`, `debug_` (or the `dbg` alias), `info `(the default) and `warn`:
```d
Logger logger = new DefaultLogger();
// Create a default logger with the default joiner
logger = new DefaultLogger();
// Test out `error()`
logger.error(["woah", "LEVELS!"], 69.420);
// Test out `info()`
logger.info(["woah", "LEVELS!"], 69.420);
// Test out `warn()`
logger.warn(["woah", "LEVELS!"], 69.420);
// Test out `debug_()`
logger.debug_(["woah", "LEVELS!"], 69.420);
```
This outputs the following:
```
[2023-Mar-03 11:33:49.2617904] (source/dlog/core.d:427): ["woah", "LEVELS!"] 69.42
[2023-Mar-03 11:33:49.2618091] (source/dlog/core.d:430): ["woah", "LEVELS!"] 69.42
[2023-Mar-03 11:33:49.2618273] (source/dlog/core.d:433): ["woah", "LEVELS!"] 69.42
[2023-Mar-03 11:33:49.2618457] (source/dlog/core.d:436): ["woah", "LEVELS!"] 69.42
```
You can also look into `logc(Context, string)` which allows you to use a `Context` object when logging, more information available in the [full API](https://dlog.dpldocs.info/v0.3.19/dlog.context.html).
You can see the [full API](https://dlog.dpldocs.info/v0.3.19/dlog.context.html) for more information.
### Custom loggers
@ -112,64 +87,32 @@ Perhaps the default transformation, `DefaultTransform`, may not be what you want
messages or perhaps don't want the date-and-timestamp included at all. All of this can be up to you if you choose to implement your own
message transform.
You will need to start off with a class that inherits from the `MessageTransform` class and then which overrides the `transform` method as shown below:
You will need to start off with a class that inherits from the `Transform` class and then which overrides the `transform` method as shown below:
```d
import dlog;
public class CustomTranform : MessageTransform
public class CustomTranform : Transform
{
public override string transform(string text, Context context)
public override Message transform(Message message)
{
BasicMessage bmesg = cast(BasicMessage)message;
// Only handle BasicMessage(s) - ones which have `setText(string)`
if(bmesg is null)
{
return message;
}
string transformed;
/* Insert transformation code here */
bmesg.setText(transformed);
/* Insert code to transform `text` and return the `transformed` text */
return transformed;
return message;
}
}
```
Additional information, besides the text being logged itself (this is the `string text` argument), comes in the form of a `Context` object `context`. What one can get from this is a `CompilationInfo` struct which contains the following fields below if one calls `toArray()` on
it which will return a string array shown below (we refer to this array as `lineInfo`):
1. `lineInfo[0]`
* This contains `__FILE_FULL_PATH__` which is the full path (absolute) to the source file where `log()` was called
2. `lineInfo[1]`
* This contains `__FILE__` which is the path (starting at `source/` to the source file where `log()` was called
3. `lineInfo[2]`
* This contains a stringified version of `__LINE__` which is the line number of the call to `log()`
4. `lineInfo[3]`
* This contains `__MODULE__` which is the name of the module the call to `log()` appeared in
5. `lineInfo[4]`
* This contains `__FUNCTION__` which is the name of the function `log()` was called in
6. `lineInfo[5]`
* This contains `__PRETTY_FUNCTION__` which is the same as above but with type information
The point of a `Context` object is also such that a custom transformer may expect a kind-of `Context` like a custom one (i.e. `CustomContext`)
which perhaps a custom logger (kind-of `Logger`) can then have set certain fields in it.
## Creating a Logger
We now need to create a logger that makes use of our message transform, we can do so by creating an instance
of the `Logger` class and passing in our `MessageTransform` as so:
```d
Logger customLogger = new DefaultLogger(new CustomTranform());
```
The above is all one needs to be able to pull off a custom transformation.
### Custom Logger
Custom loggers can also be created by sub-classing the `Logger` class and overriding the `logImpl(string)` method.
The reason someone may want to do this is up to them. One easy to think of reason is to perhaps applying filtering
of messages to be logged and skip them (as this method is where the I/O of printing out the logs normally happens).
Another reason may be to log to a different data resource, the `DefaultLogger` writes to the file descriptor `0` (stdout),
but you may want to log over a socket connection to a remote machine for example, or perhaps do several pieces of
I/O for your logging. One can do that with a custom logger, you shoudl see `source/dlog/defaults.d` for the implementation
of a custom logger, such as `DefaultLogger`.
## License
LGPL v3

264
source/dlog/basic.d Normal file
View File

@ -0,0 +1,264 @@
/**
* Defines some basic message
* types, filters and handlers
* that may be of use in
* some combination or
* seperate
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module dlog.basic;
import dlog.core;
import std.stdio : File;
/**
* Represents a basic message
* with log level information
* associated with it as well
* as text
*/
public class BasicMessage : Message
{
/**
* The text message
*/
private string text;
/**
* Log level
*/
private Level level;
/**
* Constructs a new `BasicMessage`
* with the given text and log level
*
* Params:
* text = the message text
* level = the log level (default
* is `Level.INFO`)
*/
this(string text, Level level = Level.INFO)
{
this.text = text;
this.level = level;
}
/**
* Constructs an empty message
* with the highest log level
* (least verbose)
*/
this()
{
}
/**
* Sets the text
*
* Params:
* text = the message's
* text
*/
public void setText(string text)
{
this.text = text;
}
/**
* Returns the text
*
* Returns: the text
*/
public string getText()
{
return this.text;
}
/**
* Returns the log level
*
* Returns: the level
*/
public Level getLevel()
{
return this.level;
}
/**
* Sets the log level
*
* Params:
* level = the level
*/
public void setLevel(Level level)
{
this.level = level;
}
}
/**
* A file-based handler which
* writes `BasicMessage`(s)
* to a provided file
*/
public class FileHandler : Handler
{
/**
* File to write to
*/
private File file;
/**
* Constrtucts a new
* `FileHandler` with
* the given file
*
* Params:
* file = the file
*/
this(File file)
{
this.file = file;
}
/**
* Handles the message, does a
* no-op if the message is
* not a kind-of `BasicMessage`
*
* Params:
* message = the message
*/
public void handle(Message message)
{
// Only handle BasicMessage(s)
BasicMessage bmesg = cast(BasicMessage)message;
if(bmesg)
{
file.write(bmesg.getText());
}
}
}
/**
* Logging level
*/
public enum Level
{
/**
* Error message
*/
ERROR,
/**
* Informative message
*/
INFO,
/**
* Warning message
*/
WARN,
/**
* Debugging message
*/
DEBUG
}
/**
* A level-based filter which
* has a predicate which operates
* on the value of a pointed-to
* `Level` variable
*/
private class LevelFilter : Filter
{
/**
* Address of the level var
*/
private Level* level;
/**
* Constructs a new `LevelFilter`
* with the given `Level*`
*
* Params:
* level = the level address
*/
this(Level* level)
{
this.level = level;
}
/**
* Filters the given message according
* to the current level. This will no-op
* and always return `true` if the message
* is not a kind-of `BasicMessage`
*
* Params:
* message = the message
* Returns: the verdict
*/
public bool filter(Message message)
{
// Only handle BasicMessage(s)
BasicMessage bmesg = cast(BasicMessage)message;
if(bmesg)
{
return bmesg.getLevel() <= *this.level;
}
return true;
}
}
/**
* A basic logger which has support
* for log levels
*/
public class BasicLogger : Logger
{
/**
* The current log level
*/
private Level level;
/**
* Constructs a new `BasicLogger`
*/
this()
{
// Attach a new level-filter
// with access to our current
// level
addFilter(new LevelFilter(&level));
}
/**
* Sets the log level
*
* Params:
* level = the new
* level
*/
public final void setLevel(Level level)
{
this.level = level;
}
/**
* Obtains the current log
* level
*
* Returns: the current level
*/
public final Level getLevel()
{
return this.level;
}
}

View File

@ -1,161 +0,0 @@
/**
* Context for logging
*/
module dlog.context;
import std.conv : to;
/**
* Debugging trace level
*/
public enum Level
{
/**
* Informative message
*/
INFO,
/**
* Warning message
*/
WARN,
/**
* Error message
*/
ERROR,
/**
* Debugging message
*/
DEBUG
}
/**
* Context that is passed in with the call to log
*/
public class Context
{
private CompilationInfo lineInfo;
private Level level;
/**
* Constructs a new Context
*/
this()
{
}
/**
* Set the line information
*
* Params:
* lineInfo = the CompilationInfo struct to use
*/
public final void setLineInfo(CompilationInfo lineInfo)
{
this.lineInfo = lineInfo;
}
/**
* Obtain the line information generated at compilation
* time
*
* Returns: the CompilationInfo struct
*/
public final CompilationInfo getLineInfo()
{
return lineInfo;
}
/**
* Returns the current tarce level
*
* Returns: the Level
*/
public final Level getLevel()
{
return level;
}
/**
* Set the trace level
*
* Params:
* level = the Level to use
*/
public final void setLevel(Level level)
{
this.level = level;
}
}
/**
* Information obtained during compilation time (if any)
*/
public struct CompilationInfo
{
/**
* compile time usage file
*/
public string fullFilePath;
/**
* compile time usage file (relative)
*/
public string file;
/**
* compile time usage line number
*/
public ulong line;
/**
* compile time usage module
*/
public string moduleName;
/**
* compile time usage function
*/
public string functionName;
/**
* compile time usage function (pretty)
*/
public string prettyFunctionName;
/**
* Constructs the compilation information with the provided
* parameters
*
* Params:
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
this(string fullFilePath, string file, ulong line, string moduleName, string functionName, string prettyFunctionName)
{
this.fullFilePath = fullFilePath;
this.file = file;
this.line = line;
this.moduleName = moduleName;
this.functionName = functionName;
this.prettyFunctionName = prettyFunctionName;
}
/**
* Flattens the known compile-time information into a string array
*
* Returns: a string[]
*/
public string[] toArray()
{
return [fullFilePath, file, to!(string)(line), moduleName, functionName, prettyFunctionName];
}
}

View File

@ -1,441 +1,253 @@
/**
* Core logging services
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module dlog.core;
import std.conv : to;
import std.range : join;
import dlog.transform : MessageTransform;
import dlog.defaults;
import dlog.context : Context, CompilationInfo, Level;
import dlog.utilities : flatten;
import std.container.slist : SList;
import core.sync.mutex : Mutex;
/**
* Logger
*
* Represents a logger instance
*/
public class Logger
{
/* Starting transformation */
private MessageTransform messageTransform;
/**
* The multiple argument joiner
*/
protected string multiArgJoiner;
/**
* Constructs a new Logger with the default
* MessageTransform
*
* Params:
* multiArgJoiner = optional joiner for segmented prints (default is " ")
*/
this(string multiArgJoiner = " ")
{
this(new DefaultTransform(), multiArgJoiner);
}
/**
* Constructs a new Logger with the provided
* custom message transform
* Params:
* messageTransform = the message transform to use
* multiArgJoiner = optional joiner for segmented prints (default is " ")
*/
this(MessageTransform messageTransform, string multiArgJoiner = " ")
{
this.messageTransform = messageTransform;
this.multiArgJoiner = multiArgJoiner;
}
/**
* Given an arbitrary amount of arguments, convert each to a string
* and return it as an array joined by the joiner
*
* Params:
* segments = alias sequence
* Returns: a string of the argumnets
*/
public string args(TextType...)(TextType segments)
{
/* The flattened components */
string[] components = flatten(segments);
/* Join all `components` into a single string */
string joined = join(components, multiArgJoiner);
return joined;
}
/**
* Logs the given string using the default context
*
* Params:
* text = the string to log
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
public final void log(string text, string c1 = __FILE_FULL_PATH__,
string c2 = __FILE__, ulong c3 = __LINE__,
string c4 = __MODULE__, string c5 = __FUNCTION__,
string c6 = __PRETTY_FUNCTION__)
{
/* Use the default context `Context` */
Context defaultContext = new Context();
/* Build up the line information */
CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
/* Set the line information in the context */
defaultContext.setLineInfo(compilationInfo);
/* Call the log */
logc(defaultContext, text, c1, c2, c3, c4, c5, c6);
}
/**
* Logs using the default context an arbitrary amount of arguments
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
public final void log(TextType...)(TextType segments, string c1 = __FILE_FULL_PATH__,
string c2 = __FILE__, ulong c3 = __LINE__,
string c4 = __MODULE__, string c5 = __FUNCTION__,
string c6 = __PRETTY_FUNCTION__)
{
/**
* Grab at compile-time all arguments and generate runtime code to add them to `components`
*/
string[] components = flatten(segments);
/* Join all `components` into a single string */
string messageOut = join(components, multiArgJoiner);
/* Call the log (with text and default context) */
log(messageOut, c1, c2, c3, c4, c5, c6);
}
/**
* Logs the given string using the provided context
*
* Params:
* context = the custom context to use
* text = the string to log
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
public final void logc(Context context, string text, string c1 = __FILE_FULL_PATH__,
string c2 = __FILE__, ulong c3 = __LINE__,
string c4 = __MODULE__, string c5 = __FUNCTION__,
string c6 = __PRETTY_FUNCTION__)
{
/* Build up the line information */
CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
/* Set the line information in the context */
context.setLineInfo(compilationInfo);
/* Apply the transformation on the message */
string transformedMesage = messageTransform.execute(text, context);
/* Call the underlying logger implementation */
logImpl(transformedMesage);
}
/**
* Logs using the default context an arbitrary amount of arguments
* specifically setting the context's level to ERROR
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
public void error(TextType...)(TextType segments,
string c1 = __FILE_FULL_PATH__,
string c2 = __FILE__, ulong c3 = __LINE__,
string c4 = __MODULE__, string c5 = __FUNCTION__,
string c6 = __PRETTY_FUNCTION__)
{
/* Use the default context `Context` */
Context defaultContext = new Context();
/* Build up the line information */
CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
/* Set the line information in the context */
defaultContext.setLineInfo(compilationInfo);
/* Set the level to ERROR */
defaultContext.setLevel(Level.ERROR);
/**
* Grab at compile-time all arguments and generate runtime code to add them to `components`
*/
string[] components = flatten(segments);
/* Join all `components` into a single string */
string messageOut = join(components, multiArgJoiner);
/* Call the log */
logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
}
/**
* Logs using the default context an arbitrary amount of arguments
* specifically setting the context's level to INFO
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
public void info(TextType...)(TextType segments,
string c1 = __FILE_FULL_PATH__,
string c2 = __FILE__, ulong c3 = __LINE__,
string c4 = __MODULE__, string c5 = __FUNCTION__,
string c6 = __PRETTY_FUNCTION__)
{
/* Use the default context `Context` */
Context defaultContext = new Context();
/* Build up the line information */
CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
/* Set the line information in the context */
defaultContext.setLineInfo(compilationInfo);
/* Set the level to INFO */
defaultContext.setLevel(Level.INFO);
/**
* Grab at compile-time all arguments and generate runtime code to add them to `components`
*/
string[] components = flatten(segments);
/* Join all `components` into a single string */
string messageOut = join(components, multiArgJoiner);
/* Call the log */
logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
}
/**
* Logs using the default context an arbitrary amount of arguments
* specifically setting the context's level to WARN
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
public void warn(TextType...)(TextType segments,
string c1 = __FILE_FULL_PATH__,
string c2 = __FILE__, ulong c3 = __LINE__,
string c4 = __MODULE__, string c5 = __FUNCTION__,
string c6 = __PRETTY_FUNCTION__)
{
/* Use the default context `Context` */
Context defaultContext = new Context();
/* Build up the line information */
CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
/* Set the line information in the context */
defaultContext.setLineInfo(compilationInfo);
/* Set the level to WARN */
defaultContext.setLevel(Level.WARN);
/**
* Grab at compile-time all arguments and generate runtime code to add them to `components`
*/
string[] components = flatten(segments);
/* Join all `components` into a single string */
string messageOut = join(components, multiArgJoiner);
/* Call the log */
logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
}
/**
* Logs using the default context an arbitrary amount of arguments
* specifically setting the context's level to DEBUG
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
* __FILE_FULL_PATH__ = compile time usage file
* __FILE__ = compile time usage file (relative)
* __LINE__ = compile time usage line number
* __MODULE__ = compile time usage module
* __FUNCTION__ = compile time usage function
* __PRETTY_FUNCTION__ = compile time usage function (pretty)
*/
public void debug_(TextType...)(TextType segments,
string c1 = __FILE_FULL_PATH__,
string c2 = __FILE__, ulong c3 = __LINE__,
string c4 = __MODULE__, string c5 = __FUNCTION__,
string c6 = __PRETTY_FUNCTION__)
{
/* Use the default context `Context` */
Context defaultContext = new Context();
/* Build up the line information */
CompilationInfo compilationInfo = CompilationInfo(c1, c2, c3, c4, c5, c6);
/* Set the line information in the context */
defaultContext.setLineInfo(compilationInfo);
/* Set the level to DEBUG */
defaultContext.setLevel(Level.DEBUG);
/**
* Grab at compile-time all arguments and generate runtime code to add them to `components`
*/
string[] components = flatten(segments);
/* Join all `components` into a single string */
string messageOut = join(components, multiArgJoiner);
/* Call the log */
logc(defaultContext, messageOut, c1, c2, c3, c4, c5, c6);
}
/**
* Alias for debug_
*/
public alias dbg = debug_;
/**
* Logging implementation, this is where the final
* transformed text will be transferred to and finally
* logged
*
* Params:
* message = the message to log
*/
protected abstract void logImpl(string message);
}
version(unittest)
{
import std.meta : AliasSeq;
import std.stdio : writeln;
}
/**
* Tests the DefaultLogger
*/
unittest
{
Logger logger = new DefaultLogger();
alias testParameters = AliasSeq!("This is a log message", 1.1, true, [1,2,3], 'f', logger);
// Test various types one-by-one
static foreach(testParameter; testParameters)
{
logger.log(testParameter);
}
// Test various parameters (of various types) all at once
logger.log(testParameters);
// Same as above but with a custom joiner set
logger = new DefaultLogger("(-)");
logger.log(testParameters);
writeln();
}
/**
* Printing out some mixed data-types, also using a DEFAULT context
/**
* The base message type
*/
unittest
public abstract class Message
{
Logger logger = new DefaultLogger();
// Create a default logger with the default joiner
logger = new DefaultLogger();
logger.log(["a", "b", "c", "d"], [1, 2], true);
writeln();
}
/**
* Printing out some mixed data-types, also using a CUSTOM context
/**
* Defines the filtering
* interface
*/
unittest
public interface Filter
{
Logger logger = new DefaultLogger();
// Create a default logger with the default joiner
logger = new DefaultLogger();
// Create a custom context
Context customContext = new Context();
// Log with the custom context
logger.logc(customContext, logger.args(["an", "array"], 1, "hello", true));
writeln();
/**
* Filters the given message
* returning a verdict
* based on it
*
* Params:
* message = the message
* Returns: the verdct
*/
public bool filter(Message message);
}
/**
* Printing out some mixed data-types, also using a DEFAULT context
* but also testing out the `error()`, `warn()`, `info()` and `debug()`
/**
* Defines the message
* transformation interface
*/
unittest
public interface Transform
{
Logger logger = new DefaultLogger();
/**
* Transforms the given message
*
* Params:
* message = the message input
* Returns: the transformed
* message
*/
public Message transform(Message message);
}
// Create a default logger with the default joiner
logger = new DefaultLogger();
/**
* Defines the interface
* for handling messages
*/
public interface Handler
{
/**
* Handles the given message
*
* Params:
* message = the message to
* handle
*/
public void handle(Message message);
}
// Test out `error()`
logger.error(["woah", "LEVELS!"], 69.420);
/**
* Defines the base logger
* and functionality associated
* with it
*/
public abstract class Logger
{
private SList!(Transform) transforms;
private SList!(Filter) filters;
private SList!(Handler) handlers;
private Mutex lock; // Lock for attached handlers, filters and transforms
// Test out `info()`
logger.info(["woah", "LEVELS!"], 69.420);
/**
* Constructs a new logger
*/
this()
{
this.lock = new Mutex();
}
// Test out `warn()`
logger.warn(["woah", "LEVELS!"], 69.420);
// TODO: Handle duplicate?
/**
* Adds the given transform
*
* Params:
* transform = the transform
* to add
*/
public final void addTransform(Transform transform)
{
scope(exit)
{
this.lock.unlock();
}
// Test out `debug_()`
logger.debug_(["woah", "LEVELS!"], 69.420);
this.lock.lock();
writeln();
this.transforms.insertAfter(this.transforms[], transform);
}
// TODO: Hanmdle not found explicitly?
/**
* Removes the given transform
*
* Params:
* transform = the transform
* to remove
*/
public final void removeTransform(Transform transform)
{
scope(exit)
{
this.lock.unlock();
}
this.lock.lock();
this.transforms.linearRemoveElement(transform);
}
// TODO: Handle duplicate?
/**
* Adds the given filter
*
* Params:
* filter = the filter
* to add
*/
public final void addFilter(Filter filter)
{
scope(exit)
{
this.lock.unlock();
}
this.lock.lock();
this.filters.insertAfter(this.filters[], filter);
}
// TODO: Hanmdle not found explicitly?
/**
* Removes the given filter
*
* Params:
* filter = the filter
* to remove
*/
public final void removeFilter(Filter filter)
{
scope(exit)
{
this.lock.unlock();
}
this.lock.lock();
this.filters.linearRemoveElement(filter);
}
// TODO: Handle duplicate?
/**
* Adds the given handler
*
* Params:
* handler = the handler
* to add
*/
public final void addHandler(Handler handler)
{
scope(exit)
{
this.lock.unlock();
}
this.lock.lock();
this.handlers.insertAfter(this.handlers[], handler);
}
// TODO: Hanmdle not found explicitly?
/**
* Removes the given handler
*
* Params:
* handler = the handler
* to remove
*/
public final void removeHandler(Handler handler)
{
scope(exit)
{
this.lock.unlock();
}
this.lock.lock();
this.handlers.linearRemoveElement(handler);
}
/**
* Logs the provided message by processing
* it through all the filters, and if
* the verdict is `true` then transforms
* the message via all transformers
* and finally dispatches said message
* to all attached handlers
*
* Params:
* message = the message
*/
public final void log(Message message)
{
scope(exit)
{
this.lock.unlock();
}
this.lock.lock();
foreach(Filter filter; this.filters)
{
if(!filter.filter(message))
{
return;
}
}
Message curMessage = message;
foreach(Transform transform; this.transforms)
{
curMessage = transform.transform(curMessage);
}
foreach(Handler handler; this.handlers)
{
handler.handle(curMessage);
}
}
}

View File

@ -1,20 +1,34 @@
/**
* Default logger
*/
* Default logger
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module dlog.defaults;
import dlog.core : Logger;
import dlog.transform : MessageTransform;
import dlog.context : Context, CompilationInfo;
import dlog.core;
import dlog.basic : BasicMessage, FileHandler, Level, BasicLogger;
import std.stdio : stdout;
import std.conv : to;
import dlog.utilities : flatten;
import std.array :join;
import std.datetime.systime : Clock, SysTime;
/**
* DefaultLogger
*
* The default logger logs to standard output (fd 0)
* The default logger logs using
* a pretty stock-standard (non-colored)
* message transformation and supports
* the basic levels of logging.
*/
public final class DefaultLogger : Logger
public final class DefaultLogger : BasicLogger
{
/**
* The joiner for multi-argument
* log messages
*/
private string multiArgJoiner;
/**
* Constructs a new default logger
*
@ -23,18 +37,93 @@ public final class DefaultLogger : Logger
*/
this(string multiArgJoiner = " ")
{
/* Use the DefaultTransform */
super(multiArgJoiner);
this.multiArgJoiner = multiArgJoiner;
addTransform(new DefaultTransform());
addHandler(new FileHandler(stdout));
}
/**
* Our logging implementation
* Logs the given message of an arbitrary amount of
* arguments and specifically sets the level to ERROR
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
*/
protected override void logImpl(string message)
public void error(TextType...)(TextType segments)
{
import std.stdio : write;
write(message);
doLog(segments, Level.ERROR);
}
/**
* Logs the given message of an arbitrary amount of
* arguments and specifically sets the level to INFO
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
*/
public void info(TextType...)(TextType segments)
{
doLog(segments, Level.INFO);
}
/**
* Logs the given message of an arbitrary amount of
* arguments and specifically sets the level to WARN
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
*/
public void warn(TextType...)(TextType segments)
{
doLog(segments, Level.WARN);
}
/**
* Logs the given message of an arbitrary amount of
* arguments and specifically sets the level to DEBUG
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
*/
public void debug_(TextType...)(TextType segments)
{
doLog(segments, Level.DEBUG);
}
/**
* Performs the actual logging
* by packing up everything before
* sending it to the `log(Message)`
* method
*
* Params:
* segments = the compile-time segments
* level = the log level to use
*/
private void doLog(TextType...)(TextType segments, Level level)
{
/* Create a new basic message */
BasicMessage message = new BasicMessage();
/* Set the level */
message.setLevel(level);
/**
* Grab all compile-time arguments and make them
* into an array, then join them together and
* set that text as the message's text
*/
message.setText(join(flatten(segments), multiArgJoiner));
/* Log this message */
log(message);
}
/**
* Alias for debug_
*/
public alias dbg = debug_;
}
/**
@ -42,40 +131,133 @@ public final class DefaultLogger : Logger
*
* Provides a transformation of the kind
*
* [date+time] (srcFile:lineNumber): message `\n`
* [date+time] (level): message `\n`
*/
public final class DefaultTransform : MessageTransform
private final class DefaultTransform : Transform
{
/**
* Performs the default transformation
* Performs the default transformation.
* If the message is not a `BasicMessage`
* then no transformation occurs.
*
* Params:
* text = text input to transform
* context = the context (if any)
* Returns: the transformed text
* message = the message to transform
* Returns: the transformed message
*/
public override string transform(string text, Context ctx)
public Message transform(Message message)
{
/* Extract the context */
string[] context = ctx.getLineInfo().toArray();
// Only handle BasicMessage(s)
BasicMessage bmesg = cast(BasicMessage)message;
if(bmesg is null)
{
return message;
}
string message;
string text;
/* Date and time */
import std.datetime.systime : Clock, SysTime;
SysTime currTime = Clock.currTime();
import std.conv : to;
string timestamp = to!(string)(currTime);
message = "["~timestamp~"]";
text = "["~timestamp~"]";
/* Module information */
message = message ~ "\t(";
message = message ~ context[1]~":"~context[2];
message = message ~ "): "~text;
/* Level */
text = text ~ "\t(";
text = text ~ to!(string)(bmesg.getLevel());
text = text ~ "): "~bmesg.getText();
/* Add trailing newline */
message = message ~ '\n';
text = text ~ '\n';
/* Store the updated text */
bmesg.setText(text);
return message;
}
}
version(unittest)
{
import std.meta : AliasSeq;
import std.stdio : writeln;
}
/**
* Tests the DefaultLogger
*/
unittest
{
DefaultLogger logger = new DefaultLogger();
// Set logging level to at least INFO
logger.setLevel(Level.INFO);
alias testParameters = AliasSeq!("This is a log message", 1.1, true, [1,2,3], 'f', logger);
// Test various types one-by-one
static foreach(testParameter; testParameters)
{
logger.info(testParameter);
}
// Test various parameters (of various types) all at once
logger.info(testParameters);
// Same as above but with a custom joiner set
logger = new DefaultLogger("(-)");
// Set logging level to at least INFO
logger.setLevel(Level.INFO);
logger.info(testParameters);
writeln();
}
/**
* Printing out some mixed data-types, also using a DEFAULT context
*/
unittest
{
// Create a default logger with the default joiner
DefaultLogger logger = new DefaultLogger();
// Set logging level to at least INFO
logger.setLevel(Level.INFO);
// Log some stuff
logger.info(["a", "b", "c", "d"], [1, 2], true);
writeln();
}
/**
* Printing out some mixed data-types, also using a DEFAULT context
* but also testing out the `error()`, `warn()`, `info()` and `debug()`
*/
unittest
{
// Create a default logger with the default joiner
DefaultLogger logger = new DefaultLogger();
// Set logging level to at least DEBUG
logger.setLevel(Level.DEBUG);
// Test out `error()`
logger.error(["woah", "LEVELS!"], 69.420);
// Test out `info()`
logger.info(["woah", "LEVELS!"], 69.420);
// Test out `warn()`
logger.warn(["woah", "LEVELS!"], 69.420);
// Test out `debug_()`
logger.debug_(["woah", "LEVELS!"], 69.420);
// Should not be able to see this
logger.setLevel(Level.INFO);
logger.debug_("Can't see me!");
writeln();
}

View File

@ -1,24 +1,16 @@
/**
* DLog logging facilities
*/
* The DLog logging framework
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module dlog;
/**
* Core logging services
*/
* Core logging services
*/
public import dlog.core;
/**
* Transformations
*/
public import dlog.transform;
/**
* Context for logging
*/
public import dlog.context;
/**
* Default logger
*/
* Default logger
*/
public import dlog.defaults;

View File

@ -1,52 +0,0 @@
/**
* Transformations
*/
module dlog.transform;
import dlog.context : Context;
/**
* MessageTransform
*
* A message transform takes in text, applies
* a transformation to it and outputs said text
*
* Transforms may be chained
*/
public abstract class MessageTransform
{
/* Next transformation (if any) */
private MessageTransform chainedTransform;
/**
* The actual textual transformation.
*
* This is to be implemented by sub-classes
*/
public abstract string transform(string text, Context context);
/**
* Chain a transform
*/
public final void chain(MessageTransform transform)
{
chainedTransform = transform;
}
/**
* Perform the transformation
*/
public final string execute(string text, Context context)
{
/* Perform the transformation */
string transformedText = transform(text, context);
/* If there is a chained transformation */
if(chainedTransform)
{
transformedText = chainedTransform.execute(transformedText, context);
}
return transformedText;
}
}

View File

@ -1,5 +1,7 @@
/**
* Helper functions
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module dlog.utilities;