Appended section for testing, and where to locate the testing documentation. Signed-off-by: Ethan Smith-Coss <ethan.sc@closedless.xyz>
216 lines
10 KiB
Markdown
216 lines
10 KiB
Markdown

|
||
# CLog – ClosedLess Logger
|
||
Logging as simple as putting on a shoe.
|
||
|
||
## What is CLog?
|
||
Clog (stylised as 'CLog') is a basic Python logging module which builds on top
|
||
of the built-in `print` function to provide richer outputs for general
|
||
information and debugging, to a display console, or in-depth output written to
|
||
a dedicated file.
|
||
|
||
Logging should be simple, and effective. That's why Clog is designed to do what
|
||
it does best, as simply and effectively for developers, without adding an
|
||
abundance of features. Logging can be performed simply by invoking pseudolog
|
||
methods with a message to be written out to a file (and written to console
|
||
using the `.withConsole()` method), or as advanced as calling the `printLog`
|
||
method with additional parameters found in the built-in `print` function.
|
||
|
||
It can be difficult to identify what text is debug information, warnings or
|
||
errors, from the remaining output. With this, Clog implements terminal
|
||
highlighting depending on the level which logging information is written at.
|
||
Any level which is identified as `DEBUG` or higher, is also written to the
|
||
`STDERR` pipe.
|
||
|
||
"CLog" officially stands for, 'ClosedLess Logger'. But it may be colloqually
|
||
known as some of the following:
|
||
- Cohesive Logging.
|
||
- Console Logging, and more.
|
||
- 'Clog' (noun) - a shoe with a thick wooden sole.
|
||
|
||
## How Does it Work?
|
||
As described, Clog merely acts as a wrapper over the built-in `print` function
|
||
and does not use system-level standard PIPE write operations directly. With
|
||
this, Clog exposes all keyword optional parameters of the `print` function, so
|
||
output can be modified respectfully to the `print` function.
|
||
|
||
Clog is a Singleton+Factory pattern class. This means that once the class has
|
||
been constructed by a user, another instance invoked will simply return the
|
||
defined, currently existing instance. Using a Singleton pattern allows for the
|
||
integrity of a class' state. Something like the defined logging output file
|
||
cannot ever be changed after the instance is initially constructed. This in
|
||
turn means that every instance created for the logging system will never be
|
||
out-of-sync from any other instance created. It is a Factory pattern since the class' state can be modified and returned from an invoked method.
|
||
|
||
The core wrapper responsible for invoking the `print` function with specified
|
||
optional parameters, is a private helper method called `__printLog__` of the
|
||
`Logger` class. All other methods defined in the class, in some way, call this
|
||
helper wrapper method.
|
||
|
||
Given a `TextIOWrapper` object (or of similar with the `SupportsWrite` type),
|
||
a `print` function can write directly to that object when passed as the `file=`
|
||
parameter. This introduces the `printLog2File` method, which is similar to the
|
||
`printLog` method; however, `printLog2File` is appropriate for handling strings
|
||
as pathspecs, in addition to `TextIOWrapper` objects.
|
||
|
||
It may come to reason to allow for `printLog` to handle this ability as well,
|
||
which would make `printLog2File` redundant. Well, `printLog2File` has a lot of
|
||
dedicated functionality specifically for writing to a file, which would
|
||
otherwise make `printLog` bulky and confusing. For example, `printLog2File`
|
||
allows for timestamped messages, simple stacktraces to identify what method
|
||
invoked a log call, and to ensure that ANSI escape codes (used for terminal
|
||
highlighting) are not written to file, and a lot more.
|
||
|
||
The use of pseudolog methods can make logging very simple and short, but with
|
||
the ability to not only write to file, but also to the console, lazily. A
|
||
pseudolog (or, `Logger` pseudonyms) method is a type of `Logger` method which
|
||
implies a defined level of logging. There are three pseudologs: `debug`,
|
||
`warn`, and `error`. Each of these methods are decorated with the `classmethod`
|
||
decorator to allow for the defined Singleton instance to be modified and return
|
||
itself after being invoked. From this, the `.withConsole()` method may be
|
||
invoked directly after calling a pseudolog, or later if the returned instance
|
||
from the pseudolog is stored. This is why pseudologs are considered lazy since
|
||
the call to write to console can be performed later as the information is
|
||
stored by the class' state.
|
||
|
||
**But be careful!** Pseudologs invoked with `.withConsole()` will write the
|
||
last-most message written to the file, to the console, meaning if another
|
||
pseudolog is invoked, the class state is changed, and the previous message
|
||
stored will be lost.
|
||
|
||
## How to Use CLog
|
||
Clog is very simple to use in any project you wish to start making use of
|
||
logging systems within your code base. Simply import the package, or bring
|
||
the `Logger` class into scope directly.
|
||
|
||
```py
|
||
import clog
|
||
|
||
logger = Logger()
|
||
logger.printLog("Hello, Word!")
|
||
# NOTE: 'printLog' is a static method of 'Logger'
|
||
```
|
||
or
|
||
```py
|
||
import clog
|
||
# or: from clog import Logger
|
||
|
||
clog.Logger.printLog("Hello, World!")
|
||
# or Logger.printLog(...) if brought into scope
|
||
```
|
||
|
||
### Logging based on success of code
|
||
The following is an example whereby similar behaviour can be found using the
|
||
built-in `print(..., end="")` to prevent a newline character from being
|
||
printed. The following `printLog` call afterwards will print the message and a
|
||
newline character. This allows for acknowledgement of a process about to
|
||
perform a given task, with a success statement attached to the end of the line.
|
||
|
||
```py
|
||
import os
|
||
import sys
|
||
import clog
|
||
|
||
logger = Logger(out_f="my_log_file.txt") # custom file path
|
||
my_file = "some_important_file.txt"
|
||
|
||
logger.printLog("Checking if file exists...", end="")
|
||
logger.printLog2File(f"Ensuring that file is present: {my_file}")
|
||
|
||
if not os.path.exists(my_file):
|
||
logger.printLog2File("File could not be found in the current",
|
||
"working directory. Exiting (1)...")
|
||
logger.printLog("failed.")
|
||
sys.exit(1)
|
||
|
||
logger.printLog("ok.")
|
||
logger.printLog2File("File was successfully found.")
|
||
```
|
||
|
||
### Logging with pseudologs
|
||
Sometimes it's conventient to write one message, and have it logged both to a
|
||
file, and the terminal. By using a pseudolog method, logging is guaranteed to
|
||
be written to file, and optionally the same message can be sent to the
|
||
terminal with highlighting and identical formatting using the `.withConsole()`
|
||
method.
|
||
|
||
```py
|
||
import clog
|
||
|
||
# pseudologs require accessing an instance
|
||
logger = Logger()
|
||
|
||
logger.debug("Checking integrity of operations...").withConsole()
|
||
if 1 + 1 == 2:
|
||
logger.debug("Mathematics is not broken.").withConsole()
|
||
else:
|
||
logger.error("Mathematics is broken!!").withConsole()
|
||
```
|
||
|
||
## Why Use CLog and not Another Library
|
||
It's difficult to justify a dependence into your project, especially when you
|
||
can either perform that dependence yourself, or use a different method to solve
|
||
the problem. However, here are some reasons to consider using Clog yourself:
|
||
|
||
- CLog provides you with the basic logging tools out of the box.
|
||
- Additional features, such as control over logging elevation and colouring of
|
||
text.
|
||
- CLog uses built-in functions and no external dependences (ex. testing/dev).
|
||
- There is guarantee over where information is logged to, and correctly.
|
||
- It's simple and effective to use (well documented).
|
||
- Guarantees backwards compatibility to version 3.6 and sooner.
|
||
- CLog is completely open-source under the GNU GPLv3 or later. You can inspect
|
||
the code base at will, and completely integrate the code base into your
|
||
project (with respect to the defined licence agreement).
|
||
|
||
## Comparison Between `clog` and `logging`
|
||
You might be wondering which is better: using a library that makes your code
|
||
dependent on the maintanence and upkeep of an external library; or, using a
|
||
built-in library that comes with the installation. Well it depends on the
|
||
use-case and preferences, but here are some overviews between both libraries:
|
||
|
||
- `logging` is built-in, whereas `clog` is a dependency.
|
||
- `logging` provides the ability to have multiple logging instances. `clog` is
|
||
a Singleton pattern, which can be re-instantiated if desired.
|
||
- `logging` has basic and advanced features depending on how much you want to
|
||
get out of a logging utility. `clog` just works as its intended.
|
||
- Basic output from `logging` is simple, but ugly. `clog` provides a clear
|
||
indication of log elevation using terminal hightlighting, as well as conforming
|
||
to defined standards, such as ISO 8601 timestamps in log files out-of-the-box.
|
||
- `logging` requires configuration to get more advanced behaviour. `clog`
|
||
provides majority of this behaviour without configuration, and further
|
||
configuration can be performed when logging.
|
||
- `logging` provides "handlers" for separate stream and file outputs. `clog`
|
||
handles this under-the-hood, but allows for changing between stream and file
|
||
pipes where applicable. `clog` can also do both at the same time if desired.
|
||
- Tracebacks can be essential to debugging problems, and both libraries
|
||
provide a traceback. `logging` provides this functionality during an exception
|
||
being raised. `clog` provides a basic traceback with every messaged written to
|
||
a log file (unless disabled on write), stating where the call was made to log.
|
||
- `logging` provides handlers to write to more than just streams and files. It
|
||
can write to sockets, emails, OS event systems, and more. `clog` only handles
|
||
stream and file writes; however, if `print()` can write to it, so can `clog`.
|
||
|
||
These are just some key comparisons between both libraries, and whether or not
|
||
each point is advantageous for one library or the other depends on the
|
||
developer's use-case – it does not mean one is objectively better than the
|
||
other. Which library is used depends on the use-case, but both could be used.
|
||
|
||
## Testing
|
||
If you wish to perform your own testing on the code base, create new unit
|
||
tests for new features to the project, or create more unit tests for existing
|
||
functionality, then consult the `TESTING.md` documentation file under `testing`
|
||
on how to go about this. Optionally, if you wish to understand how automated
|
||
testing is performed for Clog, you may also consult the same file for more
|
||
information.
|
||
|
||
## Licence
|
||
This project is licensed under the GNU GPLv3 or later licence agreement, which
|
||
hereby states that this software does not come with warranty or liability.
|
||
|
||
For more, please consult the `LICENCE.txt` file.
|
||
|
||
## Author
|
||
Ethan Smith-Coss (`ethan.sc@closedless.xyz`)
|
||
|
||
Copyright (c) 2022-23
|