diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 596956f9f62..cf759e3bff9 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -6,6 +6,7 @@ * [#4990](https://github.com/netbox-community/netbox/issues/4990) - Restore change logging during custom script execution * [#5004](https://github.com/netbox-community/netbox/issues/5004) - Permit assignment of an interface to a LAG on any peer virtual chassis member +* [#5012](https://github.com/netbox-community/netbox/issues/5012) - Return details of exceptions resulting from report/script execution * [#5020](https://github.com/netbox-community/netbox/issues/5020) - Correct handling of dependent objects during bulk deletion --- diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index f3810e7c566..e57caf091aa 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -652,15 +652,13 @@ class JobResult(models.Model): def set_status(self, status): """ - Helper method to change the status of the job result and save. If the target status is terminal, the - completion time is also set. + Helper method to change the status of the job result. If the target status is terminal, the completion + time is also set. """ self.status = status if status in JobResultStatusChoices.TERMINAL_STATE_CHOICES: self.completed = timezone.now() - self.save() - @classmethod def enqueue_job(cls, func, name, obj_type, user, *args, **kwargs): """ diff --git a/netbox/extras/reports.py b/netbox/extras/reports.py index 439868dfd95..64fbffb46a3 100644 --- a/netbox/extras/reports.py +++ b/netbox/extras/reports.py @@ -2,10 +2,10 @@ import importlib import inspect import logging import pkgutil +import traceback from collections import OrderedDict from django.conf import settings -from django.db.models import Q from django.utils import timezone from django_rq import job @@ -79,6 +79,7 @@ def run_report(job_result, *args, **kwargs): except Exception as e: print(e) job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) + job_result.save() logging.error(f"Error during execution of report {job_result.name}") # Delete any previous terminal state results @@ -170,7 +171,7 @@ class Report(object): timezone.now().isoformat(), level, str(obj) if obj else None, - obj.get_absolute_url() if getattr(obj, 'get_absolute_url', None) else None, + obj.get_absolute_url() if hasattr(obj, 'get_absolute_url') else None, message, )) @@ -223,17 +224,25 @@ class Report(object): job_result.status = JobResultStatusChoices.STATUS_RUNNING job_result.save() - for method_name in self.test_methods: - self.active_test = method_name - test_method = getattr(self, method_name) - test_method() + try: - if self.failed: - self.logger.warning("Report failed") - job_result.status = JobResultStatusChoices.STATUS_FAILED - else: - self.logger.info("Report completed successfully") - job_result.status = JobResultStatusChoices.STATUS_COMPLETED + for method_name in self.test_methods: + self.active_test = method_name + test_method = getattr(self, method_name) + test_method() + + if self.failed: + self.logger.warning("Report failed") + job_result.status = JobResultStatusChoices.STATUS_FAILED + else: + self.logger.info("Report completed successfully") + job_result.status = JobResultStatusChoices.STATUS_COMPLETED + + except Exception as e: + stacktrace = traceback.format_exc() + self.log_failure(None, f"An exception occurred: {type(e).__name__}: {e}
{stacktrace}")
+ logger.error(f"Exception raised during report execution: {e}")
+ job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
job_result.data = self._results
job_result.completed = timezone.now()
diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py
index 1d722908983..9d538065575 100644
--- a/netbox/extras/scripts.py
+++ b/netbox/extras/scripts.py
@@ -446,32 +446,26 @@ def run_script(data, request, commit=True, *args, **kwargs):
try:
with transaction.atomic():
script.output = script.run(**kwargs)
+ job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)
if not commit:
raise AbortTransaction()
except AbortTransaction:
- pass
+ script.log_info("Database changes have been reverted automatically.")
except Exception as e:
stacktrace = traceback.format_exc()
script.log_failure(
- "An exception occurred: `{}: {}`\n```\n{}\n```".format(type(e).__name__, e, stacktrace)
+ f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
)
+ script.log_info("Database changes have been reverted due to error.")
logger.error(f"Exception raised during script execution: {e}")
- commit = False
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
finally:
- if job_result.status != JobResultStatusChoices.STATUS_ERRORED:
- job_result.data = ScriptOutputSerializer(script).data
- job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)
-
- if not commit:
- # Delete all pending changelog entries
- script.log_info(
- "Database changes have been reverted automatically."
- )
+ job_result.data = ScriptOutputSerializer(script).data
+ job_result.save()
logger.info(f"Script completed in {job_result.duration}")
diff --git a/netbox/templates/extras/report_result.html b/netbox/templates/extras/report_result.html
index e1c46fe904e..80715f2aacd 100644
--- a/netbox/templates/extras/report_result.html
+++ b/netbox/templates/extras/report_result.html
@@ -16,7 +16,7 @@
{% endif %}
{% include 'extras/inc/job_label.html' with result=result %}
- {% if result.completed and result.status != 'errored' %}
+ {% if result.completed %}