Add the ability to send logging output to apprise

This commit is contained in:
Sebastian Goscik
2023-02-25 20:42:23 +00:00
parent 6b60fac3c1
commit ce34afaf06
7 changed files with 222 additions and 76 deletions

View File

@@ -24,7 +24,7 @@ def _parse_detection_types(ctx, param, value):
return types
@click.command()
@click.command(context_settings=dict(max_content_width=100))
@click.version_option(__version__)
@click.option('--address', required=True, envvar='UFP_ADDRESS', help='Address of Unifi Protect instance')
@click.option('--port', default=443, envvar='UFP_PORT', show_default=True, help='Port of Unifi Protect instance')
@@ -134,6 +134,25 @@ all warnings, and websocket data
help="How frequently to check for file to purge.\n\nNOTE: Can create a lot of API calls, so be careful if "
"your cloud provider charges you per api call",
)
@click.option(
'--apprise-notifier',
'apprise_notifiers',
multiple=True,
envvar="APPRISE_NOTIFIERS",
help="""\b
Apprise URL for sending notifications.
E.g: ERROR,WARNING=tgram://[BOT KEY]/[CHAT ID]
You can use this parameter multiple times to use more than one notification platform.
The following notification tags are available (corresponding to the respective logging levels):
ERROR, WARNING, INFO, DEBUG, EXTRA_DEBUG, WEBSOCKET_DATA
If no tags are specified, it defaults to ERROR
More details about supported platforms can be found here: https://github.com/caronc/apprise""",
)
def main(**kwargs):
"""A Python based tool for backing up Unifi Protect event clips as they occur."""
event_listener = UnifiProtectBackup(**kwargs)

View File

@@ -0,0 +1,15 @@
import apprise
notifier = apprise.Apprise()
def add_notification_service(url):
config = apprise.AppriseConfig()
config.add_config(url, format='text')
# If not tags are specified, default to errors otherwise ALL logging will
# be spammed to the notification service
if not config.servers()[0].tags:
config.servers()[0].tags = {'ERROR'}
notifier.add(config)

View File

@@ -28,6 +28,7 @@ from unifi_protect_backup.utils import (
human_readable_size,
VideoQueue,
)
from unifi_protect_backup.notifications import notifier
logger = logging.getLogger(__name__)
@@ -67,6 +68,7 @@ class UnifiProtectBackup:
verbose: int,
download_buffer_size: int,
purge_interval: str,
apprise_notifiers: str,
sqlite_path: str = "events.sqlite",
color_logging=False,
port: int = 443,
@@ -94,7 +96,7 @@ 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)
setup_logging(verbose, color_logging, apprise_notifiers)
logger.debug("Config:")
logger.debug(f" {address=}")
@@ -116,6 +118,7 @@ class UnifiProtectBackup:
logger.debug(f" {sqlite_path=}")
logger.debug(f" download_buffer_size={human_readable_size(download_buffer_size)}")
logger.debug(f" {purge_interval=}")
logger.debug(f" {apprise_notifiers=}")
self.rclone_destination = rclone_destination
self.retention = parse_rclone_retention(retention)
@@ -154,6 +157,7 @@ class UnifiProtectBackup:
"""
try:
logger.info("Starting...")
await notifier.async_notify("Starting UniFi Protect Backup")
# Ensure `rclone` is installed and properly configured
logger.info("Checking rclone configuration...")

View File

@@ -1,12 +1,15 @@
import logging
import re
import asyncio
from typing import Optional
from typing import Optional, List
from datetime import datetime
from dateutil.relativedelta import relativedelta
from pyunifiprotect import ProtectApiClient
from apprise import NotifyType
from unifi_protect_backup import notifications
logger = logging.getLogger(__name__)
@@ -118,7 +121,41 @@ def create_logging_handler(format):
return sh
def setup_logging(verbosity: int, color_logging: bool = False) -> None:
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.
Verbosity 0: Only log info messages created by `unifi-protect-backup`, and all warnings
@@ -135,6 +172,7 @@ def setup_logging(verbosity: int, color_logging: bool = False) -> None:
Args:
verbosity (int): The desired level of verbosity
color_logging (bool): If colors should be used in the log (default=False)
apprise_notifiers (List[str]): Notification services to hook into the logger
"""
globals()['color_logging'] = color_logging
@@ -174,6 +212,13 @@ def setup_logging(verbosity: int, color_logging: bool = False) -> None:
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}"