diff --git a/README.md b/README.md index e69de29..43e72fe 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,206 @@ +# 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. + +## 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