Updated _logger.py
Module given docstrings to extensively explain the purpose and function of defined methods within the class, and class structure itself. Method `genLogHeader` has been moved to `printfmt` module and renamed `gen_log_header`, along with appropriate private attributes now global constants in the module.
This commit is contained in:
parent
a0e487a2a7
commit
6ddb43d194
182
_logger.py
182
_logger.py
|
|
@ -1,15 +1,51 @@
|
|||
from collections import namedtuple
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from io import TextIOWrapper
|
||||
from typing import TextIO, Union, TYPE_CHECKING
|
||||
from typing import TextIO, Union
|
||||
from collections import namedtuple
|
||||
|
||||
from utils import common
|
||||
from utils.printfmt import *
|
||||
|
||||
|
||||
class Logger: ... # class delcaration
|
||||
class Logger: # class redeclaration & initialisation
|
||||
"""A simple logging class to write messages directly to the console
|
||||
or to a log file.
|
||||
|
||||
Class contains a variety of methods to perform logging, all of which
|
||||
invoke a private wrapper method over the built-in `print()` function,
|
||||
with enhanced features built into the class methods.
|
||||
|
||||
Make use of pseudolog methods (`Logger` pseudonyms) to quickly, and
|
||||
effectively write a message to a log file. These pseudolog methods
|
||||
modify the class state to remember the last message logged out to a
|
||||
file, in addition to its formatting, which can then be written to
|
||||
the console using the `Logger.withConsole()` method.
|
||||
|
||||
Example of using pseudolog methods:
|
||||
```py
|
||||
>>> import clog
|
||||
>>> logger = clog.Logger()
|
||||
>>>
|
||||
>>> logger.debug("A debug message with stacktrace!")
|
||||
>>> logger.error("Whoops! This should not be here.").withConsole()
|
||||
\033[91mWhoops! This should not be here.\033[0m
|
||||
>>>
|
||||
>>> msg = "Checking if 1 + 1 = 2..."
|
||||
>>> logger.debug(msg, end="\r").withConsole()
|
||||
>>> if 1 + 1 != 2:
|
||||
... logger.error(msg + "failed.").withConsole()
|
||||
... else:
|
||||
... logger.debug(msg + "ok.").withConsole()
|
||||
```
|
||||
|
||||
Logger can output text to a console with colour, depending on its
|
||||
associated log level given. Different standard `PIPE`s can also
|
||||
be written to depending on the level of the log, or if a file
|
||||
redirect descriptor has been given. Note, colour is omitted when
|
||||
not writing to console on STDOUT or STDERR.
|
||||
"""
|
||||
## set the PIPE to STDOUT by default
|
||||
__stdpipe: TextIO = sys.stdout
|
||||
## detect if there's a redirect
|
||||
|
|
@ -17,10 +53,6 @@ class Logger: # class redeclaration & initialisation
|
|||
__IS_STDERR_REDIR: bool = os.isatty(sys.stderr.fileno())
|
||||
## the default log out file
|
||||
__DEFAULT_OUT_FILE: str = "dump.log"
|
||||
## log message constant format template
|
||||
__LOG_TMPL = "[{DATE}] [{0}] {TYPE} "
|
||||
## string time format (ISO: 8601, long-form)
|
||||
__TIMESTAMP_FMT = "%Y-%m-%dT%H:%M:%S%z"
|
||||
## log info namedtuple for storing class states
|
||||
__LOG_INFO_TUPLE = namedtuple('LogInfo',
|
||||
['isatty', 'lv', 'msg', 'sep', 'end'])
|
||||
|
|
@ -31,13 +63,20 @@ class Logger: # class redeclaration & initialisation
|
|||
|
||||
|
||||
def __new__(cls, *, out_f: str = ...) -> Logger:
|
||||
"""Construct a new instance of the class and initialise it.
|
||||
|
||||
Constructor method is used to establish the class as a
|
||||
Singleton+Factory pattern. A new instance is returned from
|
||||
the constructor, or if an existing instance is present,
|
||||
return the object of that instance.
|
||||
"""
|
||||
if cls.__instance__ is None:
|
||||
cls.__instance__ = super(Logger, cls).__new__(cls) # establish singleton instance
|
||||
cls.log = cls.__DEFAULT_OUT_FILE # initialise attribute to default
|
||||
## handle if a custom file pathspec was given
|
||||
if out_f is not Ellipsis and isinstance(out_f, str):
|
||||
## verify path and convert to real pathspec.
|
||||
if common.isPathspec(out_f):
|
||||
if common.is_path_spec(out_f):
|
||||
## redefine the default log out attribute and create
|
||||
## public attribute for the currect log location
|
||||
cls.log = cls.__DEFAULT_OUT_FILE = \
|
||||
|
|
@ -55,7 +94,16 @@ class Logger: # class redeclaration & initialisation
|
|||
def debug(cls, *value: object, sep: Union[str, None] = None,
|
||||
end: Union[str, None] = None, wrapping: bool = True,
|
||||
strace: bool = True) -> Logger:
|
||||
""""""
|
||||
"""Pseudolog for writing to log file with level `LogLevel.DEBUG`.
|
||||
|
||||
Method is invoked on a `Logger` instance and will directly write
|
||||
out to a log file using the built-in `printLog2File` helper method.
|
||||
The returned object is a modified instance of the `Logger` class
|
||||
which stores information regarding what was written to the log
|
||||
file, and what formatting was applied. The `Logger.withConsole()`
|
||||
method can be invoked directly afterwards (or later) to write
|
||||
the same message to the console.
|
||||
"""
|
||||
Logger.printLog2File(*value, sep=sep, end=end,
|
||||
wrapping=wrapping, strace=strace)
|
||||
|
||||
|
|
@ -72,7 +120,16 @@ class Logger: # class redeclaration & initialisation
|
|||
def warn(cls, *value: object, sep: Union[str, None] = None,
|
||||
end: Union[str, None] = None, wrapping: bool = True,
|
||||
strace: bool = True) -> Logger:
|
||||
""""""
|
||||
"""Pseudolog for writing to log file with level `LogLevel.WARN`.
|
||||
|
||||
Method is invoked on a `Logger` instance and will directly write
|
||||
out to a log file using the built-in `printLog2File` helper method.
|
||||
The returned object is a modified instance of the `Logger` class
|
||||
which stores information regarding what was written to the log
|
||||
file, and what formatting was applied. The `Logger.withConsole()`
|
||||
method can be invoked directly afterwards (or later) to write
|
||||
the same message to the console.
|
||||
"""
|
||||
Logger.printLog2File(*value, level=LogLevel.WARN, sep=sep,
|
||||
end=end, wrapping=wrapping, strace=strace)
|
||||
|
||||
|
|
@ -88,7 +145,16 @@ class Logger: # class redeclaration & initialisation
|
|||
def error(cls, *value: object, sep: Union[str, None] = None,
|
||||
end: Union[str, None] = None, wrapping: bool = True,
|
||||
strace: bool = True) -> Logger:
|
||||
""""""
|
||||
"""Pseudolog for writing to log file with level `LogLevel.ERROR`.
|
||||
|
||||
Method is invoked on a `Logger` instance and will directly write
|
||||
out to a log file using the built-in `printLog2File` helper method.
|
||||
The returned object is a modified instance of the `Logger` class
|
||||
which stores information regarding what was written to the log
|
||||
file, and what formatting was applied. The `Logger.withConsole()`
|
||||
method can be invoked directly afterwards (or later) to write
|
||||
the same message to the console.
|
||||
"""
|
||||
Logger.printLog2File(*value, level=LogLevel.ERROR, sep=sep,
|
||||
end=end, wrapping=wrapping, strace=strace)
|
||||
|
||||
|
|
@ -102,31 +168,77 @@ class Logger: # class redeclaration & initialisation
|
|||
|
||||
@staticmethod
|
||||
def withConsole() -> None:
|
||||
"""Write the last message logged to a file to the console as
|
||||
well. That is, any message written using a pseudolog method.
|
||||
The last message is determined by a pseudolog, which modifies
|
||||
the state of the class to remember information regarding what
|
||||
was recently written out to a log file.
|
||||
|
||||
This method will write to the console according to the standard
|
||||
PIPE of each type of logging level. Colouring will be enabled
|
||||
for outputs in association to the log level. If PIPE is
|
||||
redirected to external file, colouring is disabled. Any other
|
||||
information regarding the formatting of the message is directly
|
||||
associated to the formatting used when writing to a log file.
|
||||
"""
|
||||
if not Logger.__loginfo is None:
|
||||
Logger.__printLog__(Logger.__loginfo.isatty,
|
||||
Logger.__loginfo.lv, Logger.__loginfo.msg,
|
||||
Logger.__loginfo.sep, Logger.__loginfo.end)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def genLogHeader(_type: Union[LogLevel, int, str]) -> str:
|
||||
## if the _type represents a LogLevel value, convert it to str
|
||||
if not isinstance(_type, str):
|
||||
_type = loglevel_as_str(_type)
|
||||
|
||||
## return newly formatted log message header
|
||||
return Logger.__LOG_TMPL.format('{CALLER}', DATE=\
|
||||
time.strftime(Logger.__TIMESTAMP_FMT, time.localtime()),
|
||||
TYPE=_type) + "{0}"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def printLog2File(*value: object,
|
||||
level: Union[LogLevel, int] = LogLevel.DEBUG, mode: str = 'a',
|
||||
file: Union[TextIOWrapper, str] = ..., sep: Union[str, None] = None,
|
||||
end: Union[str, None] = None, wrapping: bool = True,
|
||||
strace: bool = True, header: bool = True) -> None:
|
||||
""""""
|
||||
"""Wrapper method over the built-in `print()` function defined
|
||||
using 3.x syntax. All Familiar functionality can be passed to
|
||||
the method as found when calling `print()`, but comes with added
|
||||
features.
|
||||
|
||||
Method not to be confused with `Logger.printLog()`,
|
||||
`printLog2File` provides enhanced and guaranteed handling of
|
||||
file streams using the built-in `with` statement. `printLog()`
|
||||
can however write out to a file stream, but requires a
|
||||
`TextIOWrapper` object to be given, or omitted with `None` for
|
||||
output to the `STDOUT` stream. `printLog2File` can take a string
|
||||
pathspec as the location to a file and open the file stream to
|
||||
write into.
|
||||
|
||||
This method is designed strictly to write messages to a log file
|
||||
with ehanced features, such as line-wrapping and stacktrace. By
|
||||
default, this method will generate a log entry header with
|
||||
`strace` and `wrapping` enabled. Optionally, these can be disabled
|
||||
when calling the method. If the `header` is disabled, it means the
|
||||
given `value` is written directly to the log file. This allows for
|
||||
process controlled messages to be written, i.e., a log might be
|
||||
written employing a process is about to be performed, and append
|
||||
the values 'ok' or 'failed', depending on the finishing state of
|
||||
the process.
|
||||
|
||||
Examples of logging to file:
|
||||
```py
|
||||
>>> import clog
|
||||
>>>
|
||||
>>> log_file = ".dump.log"
|
||||
>>> clog.Logger.printLog2File("Hello from log file!", file=log_file)
|
||||
>>> # note, we can still pass a TextIOWrapper object
|
||||
>>> with open(log_file, 'a') as f:
|
||||
... clog.Logger.printLog2File("Using own wapper.", file=f)
|
||||
...
|
||||
>>> # process controlled logging
|
||||
>>> clog.Logger.printLog2File("Establishing OS...", end="")
|
||||
>>> import os
|
||||
>>> clog.Logger.printLog2File(os.name, header=False)
|
||||
```
|
||||
|
||||
NOTE: if `file` is omitted when invoking method, the default
|
||||
pathspec is used to write to file (defined as `__DEFAULT_OUT_FILE`).
|
||||
If a new `Logger` instance was established, when `file` is obmitted,
|
||||
the default pathspec used is defined by the `Logger` instance.
|
||||
"""
|
||||
## handle if no file parameter was given
|
||||
if file is Ellipsis or not os.path.exists(file):
|
||||
file = Logger.__DEFAULT_OUT_FILE
|
||||
|
|
@ -138,7 +250,7 @@ class Logger: # class redeclaration & initialisation
|
|||
os.getcwd()).strip('\\\/')
|
||||
## generate new header for log file and construct new message
|
||||
if header:
|
||||
msg = Logger.genLogHeader(level).format(
|
||||
msg = gen_log_header(level).format(
|
||||
" ".join(value), CALLER="{0}:{1}[{2}]".format(
|
||||
_fname, _frame.f_code.co_name, _frame.f_lineno
|
||||
).replace("module", "global") if strace else "LOGGER"
|
||||
|
|
@ -170,7 +282,7 @@ class Logger: # class redeclaration & initialisation
|
|||
the method as found when calling `print()`, but comes with added
|
||||
features.
|
||||
|
||||
`IO.printLog` is designed for purpose of logging information to
|
||||
`Logger.printLog` is designed for purpose of logging information to
|
||||
the console window or to a file, either via an explicit write by
|
||||
passing a compatible `SupportsWrite[str]` value to `file=`, or
|
||||
by redirecting the standard PIPE streams to an external file.
|
||||
|
|
@ -185,19 +297,19 @@ class Logger: # class redeclaration & initialisation
|
|||
formatted text to the standard stream. Log level can be
|
||||
elevated by either passing an integer to represent the log level,
|
||||
or pass an enum variable from `class LogLevel` from the
|
||||
`utils/printfmt.py` module.
|
||||
`utils/common.py` module.
|
||||
|
||||
Examples of logging:
|
||||
```
|
||||
>>> import utils
|
||||
>>> utils.IO.printLog("Hello, World!")
|
||||
>>> import clog
|
||||
>>> clog.Logger.printLog("Hello, World!")
|
||||
Hello, World!
|
||||
>>> utils.IO.printLog("Hello,", "World" + "!", level=LogLevel.DEBUG)
|
||||
>>> clog.Logger.printLog("Hello,", "World" + "!", level=LogLevel.DEBUG)
|
||||
\033[94mHello, World!\033[0m
|
||||
>>> utils.IO.printLog("Hello,", end=" ") ; utils.IO.printLog("World!", level=1)
|
||||
>>> clog.Logger.printLog("Hello,", end=" ") ; clog.Logger.printLog("World!", level=1)
|
||||
Hello, \033[92mWorld!\033[0m
|
||||
>>> with open("dump.log", 'a') as log_file:
|
||||
... utils.IO.printLog("Hello, Log File!", file=log_file)
|
||||
... clog.Logger.printLog("Hello, Log File!", file=log_file)
|
||||
...
|
||||
>>>
|
||||
```
|
||||
|
|
@ -239,11 +351,15 @@ class Logger: # class redeclaration & initialisation
|
|||
def __printLog__(isatty: bool, lv: LogLevel, msg: object,
|
||||
s: Union[str, None] = None, e: Union[str, None] = None,
|
||||
flsh: bool = True) -> None:
|
||||
"""Private helper method responsible for invoking the built-in
|
||||
`print` function with appropriate keyword arugments. Method
|
||||
identifies the PIPE used and provide text highlighting accordingly.
|
||||
"""
|
||||
## handle if we have a redirect
|
||||
if isatty and (Logger.__stdpipe is sys.stdout or \
|
||||
Logger.__stdpipe is sys.stderr):
|
||||
## write ANSI code to start coloured text
|
||||
print(logAsCol(lv), end="", file=Logger.__stdpipe, flush=flsh)
|
||||
print(log_as_col(lv), end="", file=Logger.__stdpipe, flush=flsh)
|
||||
## unpack the object and pass to print
|
||||
print(*msg, sep=s, end="", file=Logger.__stdpipe, flush=flsh)
|
||||
## reset the colour sequence back to normal
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user