"""Module to provide support for logging with typical parameters"""
import logging
import sys
from logging.handlers import TimedRotatingFileHandler
from typing import Optional, Dict
##pylint: disable=unused-import
##pylint: disable=missing-function-docstring
##pylint: disable=missing-class-docstring
# ANSI escape codes, picked from library Colorama to avoid dependency
CSI = "\033["
OSC = "\033]"
BEL = "\a"
[docs]
def code_to_chars(code):
return CSI + str(code) + "m"
[docs]
def set_title(title):
return OSC + "2;" + title + BEL
[docs]
def clear_screen(mode=2):
return CSI + str(mode) + "J"
[docs]
def clear_line(mode=2):
return CSI + str(mode) + "K"
[docs]
class AnsiCodes(object):
"""object to contain ansi codes for the logger function to use"""
def __init__(self):
# the subclasses declare class attributes which are numbers.
# Upon instantiation we define instance attributes, which are the same
# as the class attributes but wrapped with the ANSI escape sequence
for name in dir(self):
if not name.startswith("_"):
value = getattr(self, name)
setattr(self, name, code_to_chars(value))
[docs]
class AnsiCursor(object):
def UP(self, n=1):
return CSI + str(n) + "A"
def DOWN(self, n=1):
return CSI + str(n) + "B"
def FORWARD(self, n=1):
return CSI + str(n) + "C"
def BACK(self, n=1):
return CSI + str(n) + "D"
def POS(self, x=1, y=1):
return CSI + str(y) + ";" + str(x) + "H"
[docs]
class AnsiFore(AnsiCodes):
BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
WHITE = 37
RESET = 39
# These are fairly well supported, but not part of the standard.
LIGHTBLACK_EX = 90
LIGHTRED_EX = 91
LIGHTGREEN_EX = 92
LIGHTYELLOW_EX = 93
LIGHTBLUE_EX = 94
LIGHTMAGENTA_EX = 95
LIGHTCYAN_EX = 96
LIGHTWHITE_EX = 97
[docs]
class AnsiBack(AnsiCodes):
BLACK = 40
RED = 41
GREEN = 42
YELLOW = 43
BLUE = 44
MAGENTA = 45
CYAN = 46
WHITE = 47
RESET = 49
# These are fairly well supported, but not part of the standard.
LIGHTBLACK_EX = 100
LIGHTRED_EX = 101
LIGHTGREEN_EX = 102
LIGHTYELLOW_EX = 103
LIGHTBLUE_EX = 104
LIGHTMAGENTA_EX = 105
LIGHTCYAN_EX = 106
LIGHTWHITE_EX = 107
[docs]
class AnsiStyle(AnsiCodes):
BRIGHT = 1
DIM = 2
NORMAL = 22
RESET_ALL = 0
Fore = AnsiFore()
Back = AnsiBack()
Style = AnsiStyle()
Cursor = AnsiCursor()
formatter_screen = ColoredFormatter(
"{asctime} - {color} {levelname:8} {reset} - {message}",
style="{",
datefmt="%Y-%m-%d %H:%M:%S",
colors={
"DEBUG": Fore.CYAN,
"INFO": Fore.GREEN,
"WARNING": Fore.YELLOW + Back.BLACK,
"ERROR": Fore.RED + Back.BLACK,
"CRITICAL": Fore.RED + Back.WHITE + Style.BRIGHT,
},
)
[docs]
def setup_logging(
logfile="./audit.log", level_screen=logging.INFO, level_file=logging.INFO
):
"""Configure the logging both to screen and a file with sensible parameters
By default it will generate an audit.log file with daily rotation kept 7 days.
You can explore changing this to other kind of rotation/retention criteria.
By default it will log at INFO level. I tried with DEBUG but some libraries
start to log a lot of garbage (e.g. faker) and I had to settle on INFO.
Parameters
----------
logfile : str, optional, default "./audit.log"
Path to the log file
level_screen: int, optional, default logging.INFO
Level of logging to screen
level_file: int, optional, default logging.INFO
Level of logging to file
Returns
-------
logger : logging.Logger
The logger object
"""
log = logging.getLogger()
log.handlers.clear()
log.setLevel(logging.DEBUG)
# This is size based rotating log
# fh = RotatingFileHandler(logfile, maxBytes=50000, backupCount=7)
# This is to keep last week daily log
fh = TimedRotatingFileHandler(logfile, when="midnight", backupCount=7)
fh.setLevel(level_file)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(level_screen)
formatter = logging.Formatter(
"%(asctime)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M"
)
ch.setFormatter(formatter_screen)
fh.setFormatter(formatter)
log.addHandler(fh)
log.addHandler(ch)
return log
[docs]
def start_logging_info():
"Wrapper for setup_logging() to start logging at INFO level, with default parameters"
logger = setup_logging(
logfile="./audit.log", level_screen=logging.INFO, level_file=logging.INFO
)
logger.info("Logging started at INFO level")
return logger
[docs]
def start_logging_debug():
"Wrapper for setup_logging() to start logging at DEBUG level, with default parameters"
logger = setup_logging(
logfile="./audit.log", level_screen=logging.DEBUG, level_file=logging.DEBUG
)
logger.info("Logging started at DEBUG level")
return logger