mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
Ensure an error gets logged when a delivery method fails (#3144)
* Ensure an error gets logged when a delivery method fails - Refactor existing code to log a custom error to the database - Limit error notifications to UI * Adjust unit test * Clear existing notifications before run * Add some debug to work out what is going on * Accommodate extra notification
This commit is contained in:
parent
5870b21a91
commit
c0148c0a38
@ -9,7 +9,6 @@ import traceback
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.debug import ExceptionReporter
|
|
||||||
|
|
||||||
import rest_framework.views as drfviews
|
import rest_framework.views as drfviews
|
||||||
from error_report.models import Error
|
from error_report.models import Error
|
||||||
@ -18,6 +17,23 @@ from rest_framework.exceptions import ValidationError as DRFValidationError
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
def log_error(path):
|
||||||
|
"""Log an error to the database.
|
||||||
|
|
||||||
|
- Uses python exception handling to extract error details
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
path: The 'path' (most likely a URL) associated with this error (optional)
|
||||||
|
"""
|
||||||
|
kind, info, data = sys.exc_info()
|
||||||
|
Error.objects.create(
|
||||||
|
kind=kind.__name__,
|
||||||
|
info=info,
|
||||||
|
data='\n'.join(traceback.format_exception(kind, info, data)),
|
||||||
|
path=path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def exception_handler(exc, context):
|
def exception_handler(exc, context):
|
||||||
"""Custom exception handler for DRF framework.
|
"""Custom exception handler for DRF framework.
|
||||||
|
|
||||||
@ -55,16 +71,7 @@ def exception_handler(exc, context):
|
|||||||
|
|
||||||
response = Response(response_data, status=500)
|
response = Response(response_data, status=500)
|
||||||
|
|
||||||
# Log the exception to the database, too
|
log_error(context['request'].path)
|
||||||
kind, info, data = sys.exc_info()
|
|
||||||
|
|
||||||
Error.objects.create(
|
|
||||||
kind=kind.__name__,
|
|
||||||
info=info,
|
|
||||||
data='\n'.join(traceback.format_exception(kind, info, data)),
|
|
||||||
path=context['request'].path,
|
|
||||||
html=ExceptionReporter(context['request'], kind, info, data).get_traceback_html(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if response is not None:
|
if response is not None:
|
||||||
# Convert errors returned under the label '__all__' to 'non_field_errors'
|
# Convert errors returned under the label '__all__' to 'non_field_errors'
|
||||||
|
@ -475,6 +475,7 @@ def after_error_logged(sender, instance: Error, created: bool, **kwargs):
|
|||||||
'inventree.error_log',
|
'inventree.error_log',
|
||||||
context=context,
|
context=context,
|
||||||
targets=users,
|
targets=users,
|
||||||
|
delivery_methods=set([common.notifications.UIMessageNotification]),
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
@ -21,7 +20,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||||
from djmoney.contrib.exchange.models import convert_money
|
from djmoney.contrib.exchange.models import convert_money
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
from error_report.models import Error
|
|
||||||
from markdownx.models import MarkdownxField
|
from markdownx.models import MarkdownxField
|
||||||
from mptt.models import TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
|
|
||||||
@ -29,6 +27,7 @@ import InvenTree.helpers
|
|||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
from common.settings import currency_code_default
|
from common.settings import currency_code_default
|
||||||
from company.models import Company, SupplierPart
|
from company.models import Company, SupplierPart
|
||||||
|
from InvenTree.exceptions import log_error
|
||||||
from InvenTree.fields import InvenTreeModelMoneyField, RoundingDecimalField
|
from InvenTree.fields import InvenTreeModelMoneyField, RoundingDecimalField
|
||||||
from InvenTree.helpers import (decimal2string, getSetting, increment,
|
from InvenTree.helpers import (decimal2string, getSetting, increment,
|
||||||
notify_responsible)
|
notify_responsible)
|
||||||
@ -186,13 +185,7 @@ class Order(MetadataMixin, ReferenceIndexingMixin):
|
|||||||
# Record the error, try to press on
|
# Record the error, try to press on
|
||||||
kind, info, data = sys.exc_info()
|
kind, info, data = sys.exc_info()
|
||||||
|
|
||||||
Error.objects.create(
|
log_error('order.get_total_price')
|
||||||
kind=kind.__name__,
|
|
||||||
info=info,
|
|
||||||
data='\n'.join(traceback.format_exception(kind, info, data)),
|
|
||||||
path='order.get_total_price',
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.error(f"Missing exchange rate for '{target_currency}'")
|
logger.error(f"Missing exchange rate for '{target_currency}'")
|
||||||
|
|
||||||
# Return None to indicate the calculated price is invalid
|
# Return None to indicate the calculated price is invalid
|
||||||
@ -208,15 +201,8 @@ class Order(MetadataMixin, ReferenceIndexingMixin):
|
|||||||
total += line.quantity * convert_money(line.price, target_currency)
|
total += line.quantity * convert_money(line.price, target_currency)
|
||||||
except MissingRate:
|
except MissingRate:
|
||||||
# Record the error, try to press on
|
# Record the error, try to press on
|
||||||
kind, info, data = sys.exc_info()
|
|
||||||
|
|
||||||
Error.objects.create(
|
|
||||||
kind=kind.__name__,
|
|
||||||
info=info,
|
|
||||||
data='\n'.join(traceback.format_exception(kind, info, data)),
|
|
||||||
path='order.get_total_price',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
log_error('order.get_total_price')
|
||||||
logger.error(f"Missing exchange rate for '{target_currency}'")
|
logger.error(f"Missing exchange rate for '{target_currency}'")
|
||||||
|
|
||||||
# Return None to indicate the calculated price is invalid
|
# Return None to indicate the calculated price is invalid
|
||||||
|
@ -616,6 +616,8 @@ class BaseNotificationIntegrationTest(InvenTreeTestCase):
|
|||||||
# reload notification methods
|
# reload notification methods
|
||||||
storage.collect(run_class)
|
storage.collect(run_class)
|
||||||
|
|
||||||
|
NotificationEntry.objects.all().delete()
|
||||||
|
|
||||||
# There should be no notification runs
|
# There should be no notification runs
|
||||||
self.assertEqual(NotificationEntry.objects.all().count(), 0)
|
self.assertEqual(NotificationEntry.objects.all().count(), 0)
|
||||||
|
|
||||||
@ -630,8 +632,8 @@ class BaseNotificationIntegrationTest(InvenTreeTestCase):
|
|||||||
self.part.set_starred(self.user, True)
|
self.part.set_starred(self.user, True)
|
||||||
self.part.save()
|
self.part.save()
|
||||||
|
|
||||||
# There should be 1 notification
|
# There should be 1 (or 2) notifications - in some cases an error is generated, which creates a subsequent notification
|
||||||
self.assertEqual(NotificationEntry.objects.all().count(), 1)
|
self.assertIn(NotificationEntry.objects.all().count(), [1, 2])
|
||||||
|
|
||||||
|
|
||||||
class PartNotificationTest(BaseNotificationIntegrationTest):
|
class PartNotificationTest(BaseNotificationIntegrationTest):
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
"""Functions to print a label to a mixin printer."""
|
"""Functions to print a label to a mixin printer."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.debug import ExceptionReporter
|
|
||||||
|
|
||||||
import pdf2image
|
import pdf2image
|
||||||
from error_report.models import Error
|
|
||||||
|
|
||||||
import common.notifications
|
import common.notifications
|
||||||
|
from InvenTree.exceptions import log_error
|
||||||
from plugin.registry import registry
|
from plugin.registry import registry
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
@ -63,16 +60,7 @@ def print_label(plugin_slug: str, pdf_data, filename=None, label_instance=None,
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Log an error message to the database
|
# Log an error message to the database
|
||||||
kind, info, data = sys.exc_info()
|
log_error('plugin.print_label')
|
||||||
|
|
||||||
Error.objects.create(
|
|
||||||
kind=kind.__name__,
|
|
||||||
info=info,
|
|
||||||
data='\n'.join(traceback.format_exception(kind, info, data)),
|
|
||||||
path='print_label',
|
|
||||||
html=ExceptionReporter(None, kind, info, data).get_traceback_html(),
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.error(f"Label printing failed: Sending notification to user '{user}'") # pragma: no cover
|
logger.error(f"Label printing failed: Sending notification to user '{user}'") # pragma: no cover
|
||||||
|
|
||||||
# Throw an error against the plugin instance
|
# Throw an error against the plugin instance
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
"""Views for plugin app."""
|
"""Views for plugin app."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.views.debug import ExceptionReporter
|
|
||||||
|
|
||||||
from error_report.models import Error
|
|
||||||
|
|
||||||
|
from InvenTree.exceptions import log_error
|
||||||
from plugin.registry import registry
|
from plugin.registry import registry
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
@ -29,18 +25,8 @@ class InvenTreePluginViewMixin:
|
|||||||
try:
|
try:
|
||||||
panels += plug.render_panels(self, self.request, ctx)
|
panels += plug.render_panels(self, self.request, ctx)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Prevent any plugin error from crashing the page render
|
|
||||||
kind, info, data = sys.exc_info()
|
|
||||||
|
|
||||||
# Log the error to the database
|
# Log the error to the database
|
||||||
Error.objects.create(
|
log_error(self.request.path)
|
||||||
kind=kind.__name__,
|
|
||||||
info=info,
|
|
||||||
data='\n'.join(traceback.format_exception(kind, info, data)),
|
|
||||||
path=self.request.path,
|
|
||||||
html=ExceptionReporter(self.request, kind, info, data).get_traceback_html(),
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.error(f"Plugin '{plug.slug}' could not render custom panels at '{self.request.path}'")
|
logger.error(f"Plugin '{plug.slug}' could not render custom panels at '{self.request.path}'")
|
||||||
|
|
||||||
return panels
|
return panels
|
||||||
|
Loading…
x
Reference in New Issue
Block a user