diff --git a/README.md b/README.md index 55e7b08..3162baa 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/source/dlog/basic.d b/source/dlog/basic.d new file mode 100644 index 0000000..ed7753f --- /dev/null +++ b/source/dlog/basic.d @@ -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; + } +} \ No newline at end of file diff --git a/source/dlog/context.d b/source/dlog/context.d deleted file mode 100644 index 6e3a465..0000000 --- a/source/dlog/context.d +++ /dev/null @@ -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]; - } -} \ No newline at end of file diff --git a/source/dlog/core.d b/source/dlog/core.d index a00bb66..89cfcd2 100644 --- a/source/dlog/core.d +++ b/source/dlog/core.d @@ -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); + } + } } \ No newline at end of file diff --git a/source/dlog/defaults.d b/source/dlog/defaults.d index f11d5b9..5ae9138 100644 --- a/source/dlog/defaults.d +++ b/source/dlog/defaults.d @@ -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(); } \ No newline at end of file diff --git a/source/dlog/package.d b/source/dlog/package.d index e15ebf8..616d6ee 100644 --- a/source/dlog/package.d +++ b/source/dlog/package.d @@ -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; \ No newline at end of file diff --git a/source/dlog/transform.d b/source/dlog/transform.d deleted file mode 100644 index 3e24c62..0000000 --- a/source/dlog/transform.d +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/source/dlog/utilities.d b/source/dlog/utilities.d index cea892e..ebee2d6 100644 --- a/source/dlog/utilities.d +++ b/source/dlog/utilities.d @@ -1,5 +1,7 @@ /** * Helper functions + * + * Authors: Tristan Brice Velloza Kildaire (deavmi) */ module dlog.utilities;