"""
Offers logging utilities for the entire program.
Should be imported in every file as
"logger = logging.getLogger("logger")"
:author: Jonathan Decker
"""
import logging
import logging.config
import pathlib
import time
[docs]def setup_logger(
logger_name: str = "logger",
console_level=logging.CRITICAL,
log_file_level=logging.INFO,
logs_to_keep: int = 20,
create_log_files: bool = True,
path_to_logs: pathlib.Path = None,
) -> None:
"""
:description: Sets up a logger with formatting, log file creation, settable console and log file logging levels as
well as automatic deletion of old log files.
:param logger_name: Name of the logger profile, standard is logger, this should not be changed except for testing
:type logger_name: str
:param console_level: logging level on console, standard is INFO
:type console_level: logging level
:param log_file_level: logging level in log file, standard is DEBUG
:type log_file_level: logging level
:param logs_to_keep: number of log files to keep before deleting the oldest one, standard is 20
:type logs_to_keep: int
:param create_log_files: Whether to create log files or only log to console
:type create_log_files: bool
:param path_to_logs: path to the directory for saving the logs, standard is current working directory / logs
:type path_to_logs: pathlib.PATH
:return: None, but the logger may now be accessed via logging.getLogger(logger_name), standard is logger
:rtype: None
"""
def delete_oldest_log(path_to_logs):
"""
:description: Finds and deletes the oldest .log file in the given file path
:param path_to_logs: file path to the log folder
:type path_to_logs: pathlib.PATH
:return: None
:rtype: None
"""
# assert preconditions
assert path_to_logs.exists()
assert path_to_logs.is_dir()
assert len(list(path_to_logs.glob("*.log"))) > 0
# find oldest log
_, file_path = min((f.stat().st_mtime, f) for f in path_to_logs.glob("*.log"))
pathlib.Path.unlink(file_path)
# assert preconditions
logging_levels = [
logging.CRITICAL,
logging.ERROR,
logging.WARNING,
logging.INFO,
logging.DEBUG,
]
assert console_level in logging_levels
assert log_file_level in logging_levels
assert isinstance(logs_to_keep, int)
assert isinstance(create_log_files, bool)
assert isinstance(path_to_logs, pathlib.Path) or path_to_logs is None
if path_to_logs is None:
path_to_logs = pathlib.Path.cwd() / "logs"
assert (
len(logging.getLogger(logger_name).handlers) == 0
), f"Logger {logger_name} already has the handler: {logging.getLogger(logger_name).handlers} "
# create logger
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
logger.propagate = False
# if create_log_files is True, check for logs folder or make one
fh_did_not_fail = True
try:
if create_log_files:
if not ((path_to_logs.exists()) and path_to_logs.is_dir()):
path_to_logs.mkdir()
# check if old logs need to be deleted
if logs_to_keep >= 0:
while len(list((path_to_logs.glob("*.log")))) > logs_to_keep:
delete_oldest_log(path_to_logs)
# create file handler
time_str = time.strftime("%d-%m-%Y_%H-%M-%S")
log_file_name = f"log_{time_str}.log"
file_handler = logging.FileHandler(str(path_to_logs / log_file_name))
file_handler.setLevel(log_file_level)
except PermissionError:
fh_did_not_fail = False
# create console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(console_level)
# create formatter
formatter = logging.Formatter("%(asctime)s - %(levelname)-8s - %(message)s")
if create_log_files and fh_did_not_fail:
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# add handlers to logger
if create_log_files and fh_did_not_fail:
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.debug(f"Finished setting up {logger_name}: logger")
if not fh_did_not_fail:
logger.warning("Logger File handler failed due to missing permissions, no log file will be created")