| assets | ||
| clog | ||
| testing | ||
| .coveragerc | ||
| .gitignore | ||
| conftest.py | ||
| LICENCE.txt | ||
| pyproject.toml | ||
| README.md | ||
| requirements-dev.txt | ||
| setup.cfg | ||
| setup.py | ||
| tox.ini | ||
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.
import clog
logger = Logger()
logger.printLog("Hello, Word!")
# NOTE: 'printLog' is a static method of 'Logger'
or
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.
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.
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:
loggingis built-in, whereasclogis a dependency.loggingprovides the ability to have multiple logging instances.clogis a Singleton pattern, which can be re-instantiated if desired.logginghas basic and advanced features depending on how much you want to get out of a logging utility.clogjust works as its intended.- Basic output from
loggingis simple, but ugly.clogprovides 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. loggingrequires configuration to get more advanced behaviour.clogprovides majority of this behaviour without configuration, and further configuration can be performed when logging.loggingprovides "handlers" for separate stream and file outputs.cloghandles this under-the-hood, but allows for changing between stream and file pipes where applicable.clogcan also do both at the same time if desired.- Tracebacks can be essential to debugging problems, and both libraries
provide a traceback.
loggingprovides this functionality during an exception being raised.clogprovides a basic traceback with every messaged written to a log file (unless disabled on write), stating where the call was made to log. loggingprovides handlers to write to more than just streams and files. It can write to sockets, emails, OS event systems, and more.clogonly handles stream and file writes; however, ifprint()can write to it, so canclog.
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.
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