mirror of
https://github.com/ep1cman/unifi-protect-backup.git
synced 2025-12-05 23:53:30 +00:00
Add the ability to send logging output to apprise
This commit is contained in:
117
README.md
117
README.md
@@ -112,80 +112,77 @@ Usage: unifi-protect-backup [OPTIONS]
|
||||
|
||||
Options:
|
||||
--version Show the version and exit.
|
||||
--address TEXT Address of Unifi Protect instance
|
||||
[required]
|
||||
--port INTEGER Port of Unifi Protect instance [default:
|
||||
443]
|
||||
--username TEXT Username to login to Unifi Protect instance
|
||||
[required]
|
||||
--address TEXT Address of Unifi Protect instance [required]
|
||||
--port INTEGER Port of Unifi Protect instance [default: 443]
|
||||
--username TEXT Username to login to Unifi Protect instance [required]
|
||||
--password TEXT Password for Unifi Protect user [required]
|
||||
--verify-ssl / --no-verify-ssl Set if you do not have a valid HTTPS
|
||||
Certificate for your instance [default:
|
||||
verify-ssl]
|
||||
--rclone-destination TEXT `rclone` destination path in the format
|
||||
{rclone remote}:{path on remote}. E.g.
|
||||
`gdrive:/backups/unifi_protect` [required]
|
||||
--retention TEXT How long should event clips be backed up
|
||||
for. Format as per the `--max-age` argument
|
||||
of `rclone`
|
||||
(https://rclone.org/filtering/#max-age-don-
|
||||
t-transfer-any-file-older-than-this)
|
||||
[default: 7d]
|
||||
--rclone-args TEXT Optional extra arguments to pass to `rclone
|
||||
rcat` directly. Common usage for this would
|
||||
be to set a bandwidth limit, for example.
|
||||
--detection-types TEXT A comma separated list of which types of
|
||||
detections to backup. Valid options are:
|
||||
`motion`, `person`, `vehicle`, `ring`
|
||||
--verify-ssl / --no-verify-ssl Set if you do not have a valid HTTPS Certificate for your
|
||||
instance [default: verify-ssl]
|
||||
--rclone-destination TEXT `rclone` destination path in the format {rclone remote}:{path on
|
||||
remote}. E.g. `gdrive:/backups/unifi_protect` [required]
|
||||
--retention TEXT How long should event clips be backed up for. Format as per the
|
||||
`--max-age` argument of `rclone`
|
||||
(https://rclone.org/filtering/#max-age-don-t-transfer-any-file-
|
||||
older-than-this) [default: 7d]
|
||||
--rclone-args TEXT Optional extra arguments to pass to `rclone rcat` directly.
|
||||
Common usage for this would be to set a bandwidth limit, for
|
||||
example.
|
||||
--detection-types TEXT A comma separated list of which types of detections to backup.
|
||||
Valid options are: `motion`, `person`, `vehicle`, `ring`
|
||||
[default: motion,person,vehicle,ring]
|
||||
--ignore-camera TEXT IDs of cameras for which events should not
|
||||
be backed up. Use multiple times to ignore
|
||||
multiple IDs. If being set as an environment
|
||||
variable the IDs should be separated by
|
||||
whitespace.
|
||||
--file-structure-format TEXT A Python format string used to generate the
|
||||
file structure/name on the rclone remote.For
|
||||
details of the fields available, see the
|
||||
projects `README.md` file. [default: {camer
|
||||
a_name}/{event.start:%Y-%m-%d}/{event.end:%Y
|
||||
-%m-%dT%H-%M-%S} {detection_type}.mp4]
|
||||
--ignore-camera TEXT IDs of cameras for which events should not be backed up. Use
|
||||
multiple times to ignore multiple IDs. If being set as an
|
||||
environment variable the IDs should be separated by whitespace.
|
||||
--file-structure-format TEXT A Python format string used to generate the file structure/name
|
||||
on the rclone remote.For details of the fields available, see
|
||||
the projects `README.md` file. [default: {camera_name}/{event.s
|
||||
tart:%Y-%m-%d}/{event.end:%Y-%m-%dT%H-%M-%S}
|
||||
{detection_type}.mp4]
|
||||
-v, --verbose How verbose the logging output should be.
|
||||
|
||||
None: Only log info messages created by
|
||||
`unifi-protect-backup`, and all warnings
|
||||
None: Only log info messages created by `unifi-protect-
|
||||
backup`, and all warnings
|
||||
|
||||
-v: Only log info & debug messages
|
||||
created by `unifi-protect-backup`, and
|
||||
all warnings
|
||||
-v: Only log info & debug messages created by `unifi-
|
||||
protect-backup`, and all warnings
|
||||
|
||||
-vv: Log info & debug messages created
|
||||
by `unifi-protect-backup`, command
|
||||
output, and all warnings
|
||||
-vv: Log info & debug messages created by `unifi-protect-
|
||||
backup`, command output, and all warnings
|
||||
|
||||
-vvv Log debug messages created by
|
||||
`unifi-protect-backup`, command output,
|
||||
all info messages, and all warnings
|
||||
-vvv Log debug messages created by `unifi-protect-backup`,
|
||||
command output, all info messages, and all warnings
|
||||
|
||||
-vvvv: Log debug messages created by
|
||||
`unifi-protect-backup` command output,
|
||||
all info messages, all warnings, and
|
||||
-vvvv: Log debug messages created by `unifi-protect-backup`
|
||||
command output, all info messages, all warnings, and
|
||||
websocket data
|
||||
|
||||
-vvvvv: Log websocket data, command
|
||||
output, all debug messages, all info
|
||||
messages and all warnings [x>=0]
|
||||
-vvvvv: Log websocket data, command output, all debug
|
||||
messages, all info messages and all warnings [x>=0]
|
||||
--sqlite_path TEXT Path to the SQLite database to use/create
|
||||
--color-logging / --plain-logging
|
||||
Set if you want to use color in logging
|
||||
output [default: plain-logging]
|
||||
--download-buffer-size TEXT How big the download buffer should be (you
|
||||
can use suffixes like "B", "KiB", "MiB",
|
||||
"GiB") [default: 512MiB]
|
||||
Set if you want to use color in logging output [default: plain-
|
||||
logging]
|
||||
--download-buffer-size TEXT How big the download buffer should be (you can use suffixes like
|
||||
"B", "KiB", "MiB", "GiB") [default: 512MiB]
|
||||
--purge_interval TEXT How frequently to check for file to purge.
|
||||
|
||||
NOTE: Can create a lot of API calls, so be
|
||||
careful if your cloud provider charges you per
|
||||
api call [default: 1d]
|
||||
NOTE: Can create a lot of API calls, so be careful if your cloud
|
||||
provider charges you per api call [default: 1d]
|
||||
--apprise-notifier TEXT 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
|
||||
--help Show this message and exit.
|
||||
```
|
||||
|
||||
|
||||
89
poetry.lock
generated
89
poetry.lock
generated
@@ -75,9 +75,25 @@ category = "main"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "apprise"
|
||||
version = "1.3.0"
|
||||
description = "Push Notifications that work with just about every platform!"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
click = ">=5.0"
|
||||
markdown = "*"
|
||||
PyYAML = "*"
|
||||
requests = "*"
|
||||
requests-oauthlib = "*"
|
||||
|
||||
[[package]]
|
||||
name = "astroid"
|
||||
version = "2.12.13"
|
||||
version = "2.14.2"
|
||||
description = "An abstract syntax tree for Python with inference support."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -85,7 +101,7 @@ python-versions = ">=3.7.2"
|
||||
|
||||
[package.dependencies]
|
||||
lazy-object-proxy = ">=1.4.0"
|
||||
typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
|
||||
typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""}
|
||||
wrapt = [
|
||||
{version = ">=1.11,<2", markers = "python_version < \"3.11\""},
|
||||
{version = ">=1.14,<2", markers = "python_version >= \"3.11\""},
|
||||
@@ -194,7 +210,7 @@ name = "certifi"
|
||||
version = "2022.5.18.1"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
@@ -388,7 +404,7 @@ name = "importlib-metadata"
|
||||
version = "4.11.4"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "main"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
@@ -522,6 +538,20 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.4.1"
|
||||
description = "Python implementation of Markdown."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["coverage", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.3"
|
||||
@@ -582,6 +612,19 @@ category = "main"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.2.2"
|
||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["cryptography (>=3.0.0)"]
|
||||
signals = ["blinker (>=1.4.0)"]
|
||||
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.7.10"
|
||||
@@ -818,16 +861,19 @@ tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
version = "2.15.7"
|
||||
version = "2.16.2"
|
||||
description = "python code static checker"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
|
||||
[package.dependencies]
|
||||
astroid = ">=2.12.13,<=2.14.0-dev0"
|
||||
astroid = ">=2.14.2,<=2.16.0-dev0"
|
||||
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||
dill = ">=0.2"
|
||||
dill = [
|
||||
{version = ">=0.2", markers = "python_version < \"3.11\""},
|
||||
{version = ">=0.3.6", markers = "python_version >= \"3.11\""},
|
||||
]
|
||||
isort = ">=4.2.5,<6"
|
||||
mccabe = ">=0.6,<0.8"
|
||||
platformdirs = ">=2.2.0"
|
||||
@@ -943,7 +989,7 @@ name = "pyyaml"
|
||||
version = "6.0"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "main"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
@@ -967,7 +1013,7 @@ name = "requests"
|
||||
version = "2.27.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
|
||||
[package.dependencies]
|
||||
@@ -980,6 +1026,21 @@ urllib3 = ">=1.21.1,<1.27"
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "requests-oauthlib"
|
||||
version = "1.3.1"
|
||||
description = "OAuthlib authentication support for Requests."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
oauthlib = ">=3.0.0"
|
||||
requests = ">=2.0.0"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "requests-toolbelt"
|
||||
version = "0.9.1"
|
||||
@@ -1200,7 +1261,7 @@ name = "urllib3"
|
||||
version = "1.26.9"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
|
||||
[package.extras]
|
||||
@@ -1267,7 +1328,7 @@ name = "zipp"
|
||||
version = "3.8.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "main"
|
||||
optional = true
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
@@ -1281,7 +1342,7 @@ test = ["pytest", "black", "isort", "mypy", "flake8", "flake8-docstrings", "pyte
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
content-hash = "c4291adb62da91a97e4d6d5a4aac0be648838b95f96ed93228fd8aacdfce48b0"
|
||||
content-hash = "f2500345f68039f4afa452e6ead4c286dba8c1df2578c62bc570ce2b0130c606"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
@@ -1376,6 +1437,7 @@ appnope = [
|
||||
{file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
|
||||
{file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
|
||||
]
|
||||
apprise = []
|
||||
astroid = []
|
||||
asttokens = [
|
||||
{file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"},
|
||||
@@ -1672,6 +1734,7 @@ keyring = [
|
||||
{file = "keyring-23.5.1.tar.gz", hash = "sha256:dee502cdf18a98211bef428eea11456a33c00718b2f08524fd5727c7f424bffd"},
|
||||
]
|
||||
lazy-object-proxy = []
|
||||
markdown = []
|
||||
matplotlib-inline = [
|
||||
{file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"},
|
||||
{file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"},
|
||||
@@ -1774,6 +1837,7 @@ nodeenv = [
|
||||
{file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
|
||||
{file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
|
||||
]
|
||||
oauthlib = []
|
||||
orjson = []
|
||||
packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
@@ -1997,6 +2061,7 @@ requests = [
|
||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
|
||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
|
||||
]
|
||||
requests-oauthlib = []
|
||||
requests-toolbelt = [
|
||||
{file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
|
||||
{file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
|
||||
|
||||
@@ -47,6 +47,7 @@ aiosqlite = "^0.17.0"
|
||||
python-dateutil = "^2.8.2"
|
||||
aiorun = "^2022.11.1"
|
||||
pylint = {version = "^2.15.6", extras = ["dev"]}
|
||||
apprise = "^1.3.0"
|
||||
|
||||
[tool.poetry.extras]
|
||||
test = [
|
||||
|
||||
@@ -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)
|
||||
|
||||
15
unifi_protect_backup/notifications.py
Normal file
15
unifi_protect_backup/notifications.py
Normal 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)
|
||||
@@ -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...")
|
||||
|
||||
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user