Compare commits

...

49 Commits

Author SHA1 Message Date
Tristan B. Velloza Kildaire 852388df42 README
- Updated
2024-04-10 17:05:07 +02:00
Tristan B. Velloza Kildaire 55de4b727d
Merge pull request #5 from deavmi/feature/nextgen
DLog v2 - Complete re-write
2024-04-10 13:55:21 +02:00
Tristan B. Velloza Kildaire dac231866e BasicLogger
- Documented
2024-04-10 13:52:37 +02:00
Tristan B. Velloza Kildaire 1d57ab1529 ConsoleLogger
- Removed
2024-04-09 19:31:52 +02:00
Tristan B. Velloza Kildaire b29e2eb1c7 LevelFilter
- Now returns a `true` verdict if the incoming `Message` is NOT a kind-of `BasicMessage`
2024-04-09 19:31:22 +02:00
Tristan B. Velloza Kildaire 6be4be3534 Basics
- More docs
2024-04-09 19:30:56 +02:00
Tristan B. Velloza Kildaire 49f1f70f28 Basics
- Added some missing docs
2024-04-09 19:27:15 +02:00
Tristan B. Velloza Kildaire 986a3d3bdf RWADME
- Fixed up
2024-04-09 19:20:25 +02:00
Tristan B. Velloza Kildaire 8bb28de027 DefaultLogger
- Now is a kind-of `BasicLogger` (gives us log-level support

Defaults (unittests)

- Updated
2024-04-09 19:14:18 +02:00
Tristan B. Velloza Kildaire 9d8f7af4a3 Core
- Last few docs added
2024-04-09 19:04:31 +02:00
Tristan B. Velloza Kildaire 66adb8f700 Core
- Added more docs
2024-04-09 18:59:25 +02:00
Tristan B. Velloza Kildaire c53558f847 Refactored
- Cleaned up
2024-04-09 18:56:33 +02:00
Tristan B. Velloza Kildaire e935014573 Defaults
- Cleaned up
2024-04-09 18:53:32 +02:00
Tristan B. Velloza Kildaire 59ddf53efa Major refactor
- A lot of clean up
2024-04-09 17:26:55 +02:00
Tristan B. Velloza Kildaire 2f8b61fcf2 BasicMessage
- Added `setLevel(Level)`

FileHandler

- Only use `write(File, string)`, not `writeln(File, string)`
2024-04-09 14:28:33 +02:00
Tristan B. Velloza Kildaire 23d2b2ada9 Core
- Removed old code
2024-04-09 13:52:32 +02:00
Tristan B. Velloza Kildaire 6e48960022 BasicMessage
- Added `setText(string)`
2024-04-09 12:09:30 +02:00
Tristan B. Velloza Kildaire fbada61e90 BasicLogger
- Added `getLevel()`
2024-04-09 08:47:43 +02:00
Tristan B. Velloza Kildaire 5447f6dfbd ConsoleLogger
- Added new logger
2024-04-09 08:46:29 +02:00
Tristan B. Velloza Kildaire 39c2fe78fd BasicLogger
- Now stores current level in a filter of its own
- The `LevelFilter` is attached to the `BasicLogger` on construction

LevelFilter

- Now use a `Level*` rather
2024-04-09 08:44:30 +02:00
Tristan B. Velloza Kildaire 2658ed20b5 BasicMessage
- Now supports a `Level` setting

Level

- Added new enum type

LevelFilter

- Added implementation for level filtering
2024-04-09 08:41:08 +02:00
Tristan B. Velloza Kildaire b13621fd82 Logger
- You can now add and remove filters and handlers
2024-04-09 08:34:55 +02:00
Tristan B. Velloza Kildaire f6ebb070cc Logger
- Implemented `log(Message message)` which does filtering, transformation and lastly handling
2024-04-09 08:32:49 +02:00
Tristan B. Velloza Kildaire e35f6fba73 Core
- Added methods for manipulating transforms
2024-04-09 08:29:36 +02:00
Tristan B. Velloza Kildaire b6d002e711 Basic
- Setting out use cases
2024-04-09 08:24:34 +02:00
Tristan B. Velloza Kildaire d07cab3560 Core
- Setting out new API
2024-04-09 08:24:21 +02:00
Tristan B. Velloza Kildaire 0309e28ec8
Update README.md 2023-03-25 22:42:13 +02:00
Tristan B. Velloza Kildaire f46dae5d69
Create d.yml 2023-03-25 22:41:21 +02:00
Tristan B. Velloza Kildaire bb8f71c9db FInal update for now 2023-03-03 15:31:21 +02:00
Tristan B. Velloza Kildaire d1659bb6b9 Utilities
- Added docs
2023-03-03 15:30:43 +02:00
Tristan B. Velloza Kildaire cbeb844af5 Context
- Documented each enum member for the `Level` enum
- Documented all public members of `CompilationInfo`
2023-03-03 15:29:28 +02:00
Tristan B. Velloza Kildaire f54ec9f87b Defaults
- Updated documentation
2023-03-03 15:26:33 +02:00
Tristan B. Velloza Kildaire 0362ae7694 Core
- Updated documentation
2023-03-03 15:24:10 +02:00
Tristan B. Velloza Kildaire 991a9e4531 Core
- Updated documentation
2023-03-03 15:23:12 +02:00
Tristan B. Velloza Kildaire cd99b7af99 Defaults
- Updated documentation
2023-03-03 15:21:41 +02:00
Tristan B. Velloza Kildaire 9b79e6f154 Core
- Updated documentation

Context

- Updated documentation

Transform

- Updated documentation
2023-03-03 15:21:16 +02:00
Tristan B. Velloza Kildaire 2a6c0b493f - Fixed API documentation links 2023-03-03 15:17:27 +02:00
Tristan B. Velloza Kildaire c7cb58c1db Package
- Updated module documentation for `dlog` package module
- Added documentation for `dlog.core`, `dlog.transform`, `dlog.context` and `dlog.defaults` public module imports
2023-03-03 15:15:22 +02:00
Tristan B. Velloza Kildaire adf502335e - Updated 2023-03-03 15:11:11 +02:00
Tristan B. Velloza Kildaire 774c435307 - License update
- Updated .gitignore
2023-03-02 16:24:10 +02:00
Tristan B. Velloza Kildaire e392a7a568 - Updated README 2023-03-02 11:55:38 +02:00
Tristan B. Velloza Kildaire aaa786c86d Logger
- Fixed error where `multiArgJoiner` is `private` and not `protected` (meaning inherited loggers would not be able to access it)
2023-03-02 11:39:20 +02:00
Tristan B. Velloza Kildaire 23f155174e Logger
- Don't make `debug_`, `warn`, `info` or `error` final
2023-03-02 11:33:25 +02:00
Tristan B. Velloza Kildaire 484b9f949b Context
- Added documentation headers for `CompilationInfo`
2023-03-02 11:26:41 +02:00
Tristan B. Velloza Kildaire 77448e3245 Logger
- Allow calling `debug_(TextType...)(TextType)` using `dbg`
2023-03-02 11:12:35 +02:00
Tristan B. Velloza Kildaire d58c4076ed Logger
- Implemented `info(TextType...)(TextType)`
- Implemented `debug_(TextType...)(TextType)`
- Implemented `warn(TextType...)(TextType)`
2023-03-02 11:11:57 +02:00
Tristan B. Velloza Kildaire 20dc3c0058 Context
- Implemented `setLevel(Level)` and `getLevel()`
2023-03-02 11:09:45 +02:00
Tristan B. Velloza Kildaire 70dc9c0449 Logger
- Added missing header comment for the consructor for `Logger`
- Implemented `error(TextType...)(TextType)`
2023-03-02 11:09:28 +02:00
Tristan B. Velloza Kildaire 16459b2e4d Logger
- Removed aliases `logc` and `log`
- Renamed `log3Ctx` to `logc`
- Renamed `log3` to `log`
2023-03-02 10:58:42 +02:00
12 changed files with 997 additions and 442 deletions

32
.github/workflows/d.yml vendored Normal file
View File

@ -0,0 +1,32 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: D
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dlang-community/setup-dlang@4c99aa991ce7d19dd3064de0a4f2f6b2f152e2d7
- name: 'Build & Test'
run: |
# Build the project, with its main file included, without unittests
dub build --compiler=$DC
# Build and run tests, as defined by `unittest` configuration
# In this mode, `mainSourceFile` is excluded and `version (unittest)` are included
# See https://dub.pm/package-format-json.html#configurations
dub test --compiler=$DC

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
*test-library*
*.lst
libdlog.a
dub.selections.json

165
LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

120
README.md
View File

@ -1,13 +1,24 @@
![](branding/logo.png)
<p align="center">
<img src="branding/logo.png" width=220>
</p>
dlog
====
<br>
<h1 align="center">dlog</h1>
<h3 align="center"><i><b>Simple and modular logging library</i></b></h3>
---
<br>
<br
**Simple and modular logging library**
`[2021-Dec-23 11:17:35.3527637] (source/dlog/testing/thing.d:12): This is a log message`
---
[![D](https://github.com/deavmi/dlog/actions/workflows/d.yml/badge.svg)](https://github.com/deavmi/dlog/actions/workflows/d.yml)
## Usage
@ -26,38 +37,47 @@ 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
If you want to immediately begin logging text usin the defaults and don't care about implementing your own transformations then you can
simply use the defaulkt logger as follows:
If you want to immediately begin logging text using the defaults and don't care about implementing your own transformations then you can
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.
You can see the [full API](https://dlog.dpldocs.info/) for more information.
### Custom loggers
@ -67,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 CustomTransform : Transform
{
public override string transform(string text, string[] 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 string array as `string[] context`
the contents of which are described below:
1. `context[0]`
* This contains `__FILE_FULL_PATH__` which is the full path (absolute) to the source file where `log()` was called
2. `context[1]`
* This contains `__FILE__` which is the path (starting at `source/` to the source file where `log()` was called
3. `context[2]`
* This contains a stringified version of `__LINE__` which is the line number of the call to `log()`
4. `context[3]`
* This contains `__MODULE__` which is the name of the module the call to `log()` appeared in
5. `context[4]`
* This contains `__FUNCTION__` which is the name of the function `log()` was called in
6. `context[5]`
* This contains `__PRETTY_FUNCTION__` which is the same as above but with type information
7. `context[5..X]`
* This contains optional extras that were set when the `log()` function was called with the `contextExtras` set
* Example: `log("Hello world", contextExtras=[this])`
#### 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
LGPLv2
LGPL v3

View File

@ -1,10 +1,11 @@
{
"homepage": "https://deavmi.assigned.network/projects/dlog",
"authors": [
"Tristan B. Kildaire"
"Tristan B. Velloza Kildaire"
],
"copyright": "Copyright © 2021, Tristan B. Kildaire",
"copyright": "Copyright © 2023, Tristan B. Kildaire",
"description": "Simple and modular logging library",
"license": "LGPLv2",
"license": "LGPL 3.0",
"name": "dlog",
"targetType": "library",
"targetType": "library"
}

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,66 +0,0 @@
module dlog.context;
import std.conv : to;
public enum Level
{
INFO,
WARN,
ERROR,
DEBUG
}
/**
* Context that is passed in with the call to log
*/
public class Context
{
private CompilationInfo lineInfo;
private Level level;
this()
{
}
public final void setLineInfo(CompilationInfo lineInfo)
{
this.lineInfo = lineInfo;
}
public final CompilationInfo getLineInfo()
{
return lineInfo;
}
}
/**
* Information obtained during compilation time (if any)
*/
public struct CompilationInfo
{
public string fullFilePath;
public string file;
public ulong line;
public string moduleName;
public string functionName;
public string prettyFunctionName;
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;
}
public string[] toArray()
{
return [fullFilePath, file, to!(string)(line), moduleName, functionName, prettyFunctionName];
}
}

View File

@ -1,235 +1,253 @@
/**
* Core module containing types pertaining to the base Logger
* class and base MessageTransform class (along with a default
* transform, DefaultTransform)
*/
/**
* 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;
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 */
private string multiArgJoiner;
/**
* Constructs a new Logger with the default
* MessageTransform, see TODO (ref module)
*/
this(string multiArgJoiner = " ")
{
this(new DefaultTransform(), multiArgJoiner);
}
/**
* Constructs a new Logger with the given
* MessageTransform
*/
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 log3(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 */
log3Ctx(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 log3(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) */
log3(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 log3Ctx(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);
}
public alias log = log3;
public alias logc = log3Ctx;
/**
* 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();
/**
* Filters the given message
* returning a verdict
* based on it
*
* Params:
* message = the message
* Returns: the verdct
*/
public bool filter(Message message);
}
// Create a default logger with the default joiner
logger = new DefaultLogger();
/**
* Defines the message
* transformation interface
*/
public interface Transform
{
/**
* Transforms the given message
*
* Params:
* message = the message input
* Returns: the transformed
* message
*/
public Message transform(Message message);
}
// Create c custom context
Context customContext = new Context();
/**
* Defines the interface
* for handling messages
*/
public interface Handler
{
/**
* Handles the given message
*
* Params:
* message = the message to
* handle
*/
public void handle(Message message);
}
// Log with the custom context
logger.logc(customContext, logger.args(["an", "array"], 1, "hello", true));
/**
* 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
writeln();
/**
* Constructs a new logger
*/
this()
{
this.lock = new Mutex();
}
// TODO: Handle duplicate?
/**
* Adds the given transform
*
* Params:
* transform = the transform
* to add
*/
public final void addTransform(Transform transform)
{
scope(exit)
{
this.lock.unlock();
}
this.lock.lock();
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,31 +1,129 @@
/**
* Includes defaults such as the DefaultLogger
*/
* 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
*
* Params:
* multiArgJoiner = the joiner to use
*/
this(string multiArgJoiner = " ")
{
/* Use the DefaultTransform */
super(multiArgJoiner);
this.multiArgJoiner = multiArgJoiner;
addTransform(new DefaultTransform());
addHandler(new FileHandler(stdout));
}
protected override void logImpl(string message)
/**
* Logs the given message of an arbitrary amount of
* arguments and specifically sets the level to ERROR
*
* Params:
* segments = the arbitrary argumnets (alias sequence)
*/
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_;
}
/**
@ -33,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,11 +1,16 @@
/**
* Package definition module
*
* Import this to use dlog
*/
* The DLog logging framework
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module dlog;
/**
* Core logging services
*/
public import dlog.core;
public import dlog.transform;
public import dlog.defaults;
public import dlog.context;
/**
* Default logger
*/
public import dlog.defaults;

View File

@ -1,49 +0,0 @@
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,3 +1,8 @@
/**
* Helper functions
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module dlog.utilities;
import std.conv : to;