diff --git a/changedetectionio/__init__.py b/changedetectionio/__init__.py index 451148d1..eb765a70 100644 --- a/changedetectionio/__init__.py +++ b/changedetectionio/__init__.py @@ -70,6 +70,7 @@ app.config['LOGIN_DISABLED'] = False # Disables caching of the templates app.config['TEMPLATES_AUTO_RELOAD'] = True +notification_debug_log=[] def init_app_secret(datastore_path): secret = "" @@ -529,6 +530,7 @@ def changedetection_app(config=None, datastore_o=None): 'notification_title': form.notification_title.data, 'notification_body': form.notification_body.data, 'notification_format': form.notification_format.data, + 'uuid': uuid } notification_q.put(n_object) flash('Test notification queued.') @@ -765,6 +767,14 @@ def changedetection_app(config=None, datastore_o=None): uuid=uuid) return output + @app.route("/settings/notification-logs", methods=['GET']) + @login_required + def notification_logs(): + global notification_debug_log + output = render_template("notification-log.html", + logs=notification_debug_log if len(notification_debug_log) else ["No errors or warnings detected"]) + + return output @app.route("/api//snapshot/current", methods=['GET']) @login_required def api_snapshot(uuid): @@ -997,6 +1007,7 @@ def check_for_new_version(): app.config.exit.wait(86400) def notification_runner(): + global notification_debug_log while not app.config.exit.is_set(): try: # At the moment only one thread runs (single runner) @@ -1011,8 +1022,18 @@ def notification_runner(): notification.process_notification(n_object, datastore) except Exception as e: - print("Watch URL: {} Error {}".format(n_object['watch_url'], e)) - datastore.update_watch(uuid=n_object['uuid'], update_obj={'last_error': "Notification error: " + str(e)}) + print("Watch URL: {} Error {}".format(n_object['watch_url'], str(e))) + + # UUID wont be present when we submit a 'test' from the global settings + if 'uuid' in n_object: + datastore.update_watch(uuid=n_object['uuid'], update_obj={'last_error': "Notification error detected, please see logs."}) + + log_lines = str(e).splitlines() + notification_debug_log += log_lines + + # Trim the log length + notification_debug_log = notification_debug_log[-100:] + # Thread runner to check every minute, look for new watches to feed into the Queue. diff --git a/changedetectionio/notification.py b/changedetectionio/notification.py index 5c5a1fb1..54495685 100644 --- a/changedetectionio/notification.py +++ b/changedetectionio/notification.py @@ -25,9 +25,7 @@ default_notification_body = '{watch_url} had a change.\n---\n{diff}\n---\n' default_notification_title = 'ChangeDetection.io Notification - {watch_url}' def process_notification(n_object, datastore): - import logging - log = logging.getLogger('apprise') - log.setLevel('TRACE') + apobj = apprise.Apprise(debug=True) for url in n_object['notification_urls']: @@ -53,11 +51,22 @@ def process_notification(n_object, datastore): n_title = n_title.replace(token, val) n_body = n_body.replace(token, val) - apobj.notify( + # https://github.com/caronc/apprise/wiki/Development_LogCapture + # Anything higher than or equal to WARNING (which covers things like Connection errors) + # raise it as an exception + + with apprise.LogCapture(level=apprise.logging.DEBUG) as logs: + apobj.notify( body=n_body, title=n_title, - body_format=n_format, - ) + body_format=n_format) + + # Returns empty string if nothing found, multi-line string otherwise + log_value = logs.getvalue() + if log_value and 'WARNING' in log_value or 'ERROR' in log_value: + raise Exception(log_value) + + # Notification title + body content parameters get created here. def create_notification_parameters(n_object, datastore): diff --git a/changedetectionio/templates/_common_fields.jinja b/changedetectionio/templates/_common_fields.jinja index e7ecb375..4d757086 100644 --- a/changedetectionio/templates/_common_fields.jinja +++ b/changedetectionio/templates/_common_fields.jinja @@ -15,6 +15,7 @@
  • Use AppRise URLs for notification to just about any service! Please read the notification services wiki here for important configuration notes.
  • discord:// will silently fail if the total message length is more than 2000 chars.
  • tgram:// bots cant send messages to other bots, so you should specify chat ID of non-bot user.
  • +
  • Go here for Notification debug logs
  • diff --git a/changedetectionio/templates/notification-log.html b/changedetectionio/templates/notification-log.html new file mode 100644 index 00000000..26cc5cb6 --- /dev/null +++ b/changedetectionio/templates/notification-log.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block content %} +
    +
    + +

    The following issues were detected when sending notifications

    +
    +
      + {% for log in logs|reverse %} +
    • {{log}}
    • + {% endfor %} +
    +
    + +
    +
    + +{% endblock %} diff --git a/changedetectionio/templates/settings.html b/changedetectionio/templates/settings.html index 04752f1a..fa76bc7d 100644 --- a/changedetectionio/templates/settings.html +++ b/changedetectionio/templates/settings.html @@ -59,6 +59,8 @@ {{ render_common_settings_form(form, current_base_url) }} + Notification debug logs +
    diff --git a/changedetectionio/tests/test_notification.py b/changedetectionio/tests/test_notification.py index 79054d41..f77d6501 100644 --- a/changedetectionio/tests/test_notification.py +++ b/changedetectionio/tests/test_notification.py @@ -226,3 +226,32 @@ def test_check_notification(client, live_server): ) assert b"Notification Body and Title is required when a Notification URL is used" in res.data + + # Check we capture the failure, we can just use trigger_check = y here + res = client.post( + url_for("edit_page", uuid="first"), + data={"notification_urls": "jsons://broken-url.changedetection.io/test", + "notification_title": "xxx", + "notification_body": "xxxxx", + "notification_format": "Text", + "url": test_url, + "tag": "my tag", + "title": "my title", + "headers": "", + "fetch_backend": "html_requests", + "trigger_check": "y"}, + follow_redirects=True + ) + + time.sleep(3) + + # The error should show in the notification logs + res = client.get( + url_for("notification_logs")) + assert bytes("Name or service not known".encode('utf-8')) in res.data + + # And it should be listed on the watch overview + res = client.get( + url_for("index")) + assert bytes("Notification error detected".encode('utf-8')) in res.data +