Refactor logging customisations into custom handler

This commit is contained in:
Sebastian Goscik
2023-02-26 00:00:25 +00:00
parent ce34afaf06
commit fb1f266eae
2 changed files with 80 additions and 76 deletions

View File

@@ -28,7 +28,7 @@ from unifi_protect_backup.utils import (
human_readable_size,
VideoQueue,
)
from unifi_protect_backup.notifications import notifier
from unifi_protect_backup import notifications
logger = logging.getLogger(__name__)
@@ -96,7 +96,10 @@ class UnifiProtectBackup:
sqlite_path (str): Path where to find/create sqlite database
purge_interval (str): How often to check for files to delete
"""
setup_logging(verbose, color_logging, apprise_notifiers)
for notifier in apprise_notifiers:
notifications.add_notification_service(notifier)
setup_logging(verbose, color_logging)
logger.debug("Config:")
logger.debug(f" {address=}")
@@ -157,7 +160,7 @@ class UnifiProtectBackup:
"""
try:
logger.info("Starting...")
await notifier.async_notify("Starting UniFi Protect Backup")
await notifications.notifier.async_notify("Starting UniFi Protect Backup")
# Ensure `rclone` is installed and properly configured
logger.info("Checking rclone configuration...")

View File

@@ -80,81 +80,89 @@ def add_logging_level(levelName: str, levelNum: int, methodName: Optional[str] =
color_logging = False
def add_color_to_record_levelname(record):
levelno = record.levelno
if levelno >= logging.CRITICAL:
color = '\x1b[31;1m' # RED
elif levelno >= logging.ERROR:
color = '\x1b[31;1m' # RED
elif levelno >= logging.WARNING:
color = '\x1b[33;1m' # YELLOW
elif levelno >= logging.INFO:
color = '\x1b[32;1m' # GREEN
elif levelno >= logging.DEBUG:
color = '\x1b[36;1m' # CYAN
elif levelno >= logging.EXTRA_DEBUG:
color = '\x1b[35;1m' # MAGENTA
else:
color = '\x1b[0m'
return f"{color}{record.levelname}\x1b[0m"
class AppriseStreamHandler(logging.StreamHandler):
def __init__(self, color_logging, *args, **kwargs):
super().__init__(*args, **kwargs)
self.color_logging = color_logging
def emit_apprise(self, record):
loop = asyncio.get_event_loop()
msg = self.format(record)
logging_map = {
logging.ERROR: NotifyType.FAILURE,
logging.WARNING: NotifyType.WARNING,
logging.INFO: NotifyType.INFO,
logging.DEBUG: NotifyType.INFO,
logging.EXTRA_DEBUG: NotifyType.INFO,
logging.WEBSOCKET_DATA: NotifyType.INFO,
}
# Only try notifying if there are notification servers configured
# and the asyncio loop isn't closed (aka we are quitting)
if notifications.notifier.servers and not loop.is_closed():
notify = notifications.notifier.async_notify(
body=msg,
title=record.levelname,
notify_type=logging_map[record.levelno],
tag=[record.levelname],
)
if loop.is_running():
asyncio.create_task(notify)
else:
loop.run_until_complete(notify)
def emit_stream(self, record):
record.levelname = f"{record.levelname:^11s}" # Pad level name to max width
if self.color_logging:
record.levelname = add_color_to_record_levelname(record)
msg = self.format(record)
stream = self.stream
# issue 35046: merged two stream.writes into one.
stream.write(msg + self.terminator)
self.flush()
def emit(self, record):
try:
self.emit_apprise(record)
self.emit_stream(record)
except RecursionError: # See issue 36272
raise
except Exception:
self.handleError(record)
def create_logging_handler(format):
date_format = "%Y-%m-%d %H:%M:%S"
style = '{'
sh = logging.StreamHandler()
global color_logging
sh = AppriseStreamHandler(color_logging)
formatter = logging.Formatter(format, date_format, style)
sh.setFormatter(formatter)
def decorate_emit(fn):
# add methods we need to the class
def new(*args):
levelno = args[0].levelno
if levelno >= logging.CRITICAL:
color = '\x1b[31;1m' # RED
elif levelno >= logging.ERROR:
color = '\x1b[31;1m' # RED
elif levelno >= logging.WARNING:
color = '\x1b[33;1m' # YELLOW
elif levelno >= logging.INFO:
color = '\x1b[32;1m' # GREEN
elif levelno >= logging.DEBUG:
color = '\x1b[36;1m' # CYAN
elif levelno >= logging.EXTRA_DEBUG:
color = '\x1b[35;1m' # MAGENTA
else:
color = '\x1b[0m'
global color_logging
if color_logging:
args[0].levelname = f"{color}{args[0].levelname:^11s}\x1b[0m"
else:
args[0].levelname = f"{args[0].levelname:^11s}"
return fn(*args)
return new
sh.emit = decorate_emit(sh.emit)
return sh
def patch_logger_notifications(logger):
"""
Patches the core logging function to also send apprise notifications
"""
original_log = logger._log
logging_map = {
logging.ERROR: NotifyType.FAILURE,
logging.WARNING: NotifyType.WARNING,
logging.INFO: NotifyType.INFO,
logging.DEBUG: NotifyType.INFO,
logging.EXTRA_DEBUG: NotifyType.INFO,
logging.WEBSOCKET_DATA: NotifyType.INFO,
}
def new_log(self, level, msg, *args, **kwargs):
original_log(level, msg, *args, **kwargs)
loop = asyncio.get_event_loop()
if not loop.is_closed():
level_name = logging.getLevelName(level)
coro = notifications.notifier.async_notify(
body=msg, title=level_name, notify_type=logging_map[level], tag=[level_name]
)
if loop.is_running():
asyncio.create_task(coro)
else:
loop.run_until_complete(coro)
logger.__class__._log = new_log
def setup_logging(verbosity: int, color_logging: bool = False, apprise_notifiers: List[str] = []) -> None:
"""Configures loggers to provided the desired level of verbosity.
@@ -212,13 +220,6 @@ def setup_logging(verbosity: int, color_logging: bool = False, apprise_notifiers
logging.basicConfig(level=logging.DEBUG, handlers=[sh])
logger.setLevel(logging.WEBSOCKET_DATA) # type: ignore
for notifier in apprise_notifiers:
notifications.add_notification_service(notifier)
# Only send logs to notification service if it is enabled
if notifications.notifier.servers:
patch_logger_notifications(logger)
def setup_event_logger(logger):
format = "{asctime} [{levelname:^11s}] {name:<42} :{event} {message}"