Notification error log handler (#403)

* Add a notifications debug/error log interface (Link available under the notification URLs list)
pull/405/head
dgtlmoon 3 years ago committed by GitHub
parent 5a645fb74d
commit abaec224f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -70,6 +70,7 @@ app.config['LOGIN_DISABLED'] = False
# Disables caching of the templates # Disables caching of the templates
app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['TEMPLATES_AUTO_RELOAD'] = True
notification_debug_log=[]
def init_app_secret(datastore_path): def init_app_secret(datastore_path):
secret = "" secret = ""
@ -529,6 +530,7 @@ def changedetection_app(config=None, datastore_o=None):
'notification_title': form.notification_title.data, 'notification_title': form.notification_title.data,
'notification_body': form.notification_body.data, 'notification_body': form.notification_body.data,
'notification_format': form.notification_format.data, 'notification_format': form.notification_format.data,
'uuid': uuid
} }
notification_q.put(n_object) notification_q.put(n_object)
flash('Test notification queued.') flash('Test notification queued.')
@ -765,6 +767,14 @@ def changedetection_app(config=None, datastore_o=None):
uuid=uuid) uuid=uuid)
return output 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/<string:uuid>/snapshot/current", methods=['GET']) @app.route("/api/<string:uuid>/snapshot/current", methods=['GET'])
@login_required @login_required
def api_snapshot(uuid): def api_snapshot(uuid):
@ -997,6 +1007,7 @@ def check_for_new_version():
app.config.exit.wait(86400) app.config.exit.wait(86400)
def notification_runner(): def notification_runner():
global notification_debug_log
while not app.config.exit.is_set(): while not app.config.exit.is_set():
try: try:
# At the moment only one thread runs (single runner) # At the moment only one thread runs (single runner)
@ -1011,8 +1022,18 @@ def notification_runner():
notification.process_notification(n_object, datastore) notification.process_notification(n_object, datastore)
except Exception as e: except Exception as e:
print("Watch URL: {} Error {}".format(n_object['watch_url'], e)) print("Watch URL: {} Error {}".format(n_object['watch_url'], str(e)))
datastore.update_watch(uuid=n_object['uuid'], update_obj={'last_error': "Notification error: " + 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. # Thread runner to check every minute, look for new watches to feed into the Queue.

@ -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}' default_notification_title = 'ChangeDetection.io Notification - {watch_url}'
def process_notification(n_object, datastore): def process_notification(n_object, datastore):
import logging
log = logging.getLogger('apprise')
log.setLevel('TRACE')
apobj = apprise.Apprise(debug=True) apobj = apprise.Apprise(debug=True)
for url in n_object['notification_urls']: 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_title = n_title.replace(token, val)
n_body = n_body.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, body=n_body,
title=n_title, 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. # Notification title + body content parameters get created here.
def create_notification_parameters(n_object, datastore): def create_notification_parameters(n_object, datastore):

@ -15,6 +15,7 @@
<li>Use <a target=_new href="https://github.com/caronc/apprise">AppRise URLs</a> for notification to just about any service! <i><a target=_new href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">Please read the notification services wiki here for important configuration notes</a></i>.</li> <li>Use <a target=_new href="https://github.com/caronc/apprise">AppRise URLs</a> for notification to just about any service! <i><a target=_new href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">Please read the notification services wiki here for important configuration notes</a></i>.</li>
<li><code>discord://</code> will silently fail if the total message length is more than 2000 chars.</li> <li><code>discord://</code> will silently fail if the total message length is more than 2000 chars.</li>
<li><code>tgram://</code> bots cant send messages to other bots, so you should specify chat ID of non-bot user.</li> <li><code>tgram://</code> bots cant send messages to other bots, so you should specify chat ID of non-bot user.</li>
<li>Go here for <a href="{{url_for('notification_logs')}}">Notification debug logs</a></li>
</ul> </ul>
</div> </div>
</div> </div>

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block content %}
<div class="edit-form">
<div class="inner">
<h4 style="margin-top: 0px;">The following issues were detected when sending notifications</h4>
<div id="notification-customisation">
<ul style="font-size: 80%; margin:0px; padding: 0 0 0 7px">
{% for log in logs|reverse %}
<li>{{log}}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}

@ -59,6 +59,8 @@
{{ render_common_settings_form(form, current_base_url) }} {{ render_common_settings_form(form, current_base_url) }}
</div> </div>
</fieldset> </fieldset>
<a href="{{url_for('notification_logs')}}">Notification debug logs</a>
</div> </div>
<div class="tab-pane-inner" id="fetching"> <div class="tab-pane-inner" id="fetching">

@ -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 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

Loading…
Cancel
Save