diff --git a/src/backend/InvenTree/InvenTree/exceptions.py b/src/backend/InvenTree/InvenTree/exceptions.py index ac84befa93..8d25534095 100644 --- a/src/backend/InvenTree/InvenTree/exceptions.py +++ b/src/backend/InvenTree/InvenTree/exceptions.py @@ -4,6 +4,7 @@ import sys import traceback +from typing import Optional from django.conf import settings from django.core.exceptions import ValidationError as DjangoValidationError @@ -17,7 +18,13 @@ from rest_framework.response import Response logger = structlog.get_logger('inventree') -def log_error(path, error_name=None, error_info=None, error_data=None): +def log_error( + path, + error_name=None, + error_info=None, + error_data=None, + plugin: Optional[str] = None, +): """Log an error to the database. - Uses python exception handling to extract error details @@ -29,6 +36,7 @@ def log_error(path, error_name=None, error_info=None, error_data=None): error_name: The name of the error (optional, overrides 'kind') error_info: The error information (optional, overrides 'info') error_data: The error data (optional, overrides 'data') + plugin: The plugin name associated with this error (optional) """ from error_report.models import Error @@ -57,6 +65,10 @@ def log_error(path, error_name=None, error_info=None, error_data=None): # Log error to stderr logger.error(info) + if plugin: + # If a plugin is specified, prepend it to the path + path = f'plugin.{plugin}.{path}' + # Ensure the error information does not exceed field size limits path = path[:200] kind = kind[:128] diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index ee6ffe642c..4a43a799e4 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -510,7 +510,7 @@ def increment_serial_number(serial, part=None): if result is not None: return str(result) except Exception: - log_error(f'{plugin.slug}.increment_serial_number') + log_error('increment_serial_number', plugin=plugin.slug) # If we get to here, no plugins were able to "increment" the provided serial value # Attempt to perform increment according to some basic rules diff --git a/src/backend/InvenTree/InvenTree/models.py b/src/backend/InvenTree/InvenTree/models.py index a70f5b601a..0afcf47cff 100644 --- a/src/backend/InvenTree/InvenTree/models.py +++ b/src/backend/InvenTree/InvenTree/models.py @@ -102,7 +102,7 @@ class PluginValidationMixin(DiffMixin): import InvenTree.exceptions InvenTree.exceptions.log_error( - f'plugins.{plugin.slug}.validate_model_instance' + 'validate_model_instance', plugin=plugin.slug ) raise ValidationError(_('Error running plugin validation')) @@ -139,7 +139,7 @@ class PluginValidationMixin(DiffMixin): # Plugin might raise a ValidationError to prevent deletion raise e except Exception: - log_error('plugin.validate_model_deletion') + log_error('validate_model_deletion', plugin=plugin.slug) continue super().delete() diff --git a/src/backend/InvenTree/common/icons.py b/src/backend/InvenTree/common/icons.py index 76747b1954..873ae30852 100644 --- a/src/backend/InvenTree/common/icons.py +++ b/src/backend/InvenTree/common/icons.py @@ -79,7 +79,7 @@ def get_icon_packs(): try: icon_packs.extend(plugin.icon_packs()) except Exception as e: - log_error('get_icon_packs') + log_error('get_icon_packs', plugin=plugin.slug) logger.warning('Error loading icon pack from plugin %s: %s', plugin, e) _icon_packs = {pack.prefix: pack for pack in icon_packs} diff --git a/src/backend/InvenTree/data_exporter/mixins.py b/src/backend/InvenTree/data_exporter/mixins.py index 8274e68ee0..7e4229c601 100644 --- a/src/backend/InvenTree/data_exporter/mixins.py +++ b/src/backend/InvenTree/data_exporter/mixins.py @@ -332,9 +332,7 @@ class DataExportViewMixin: try: queryset = export_plugin.filter_queryset(queryset) except Exception: - InvenTree.exceptions.log_error( - f'plugins.{export_plugin.slug}.filter_queryset' - ) + InvenTree.exceptions.log_error('filter_queryset', plugin=export_plugin.slug) raise ValidationError(export_error) # Update the output instance with the total number of items to export @@ -355,7 +353,7 @@ class DataExportViewMixin: ) except Exception: InvenTree.exceptions.log_error( - f'plugins.{export_plugin.slug}.generate_filename' + 'generate_filename', plugin=export_plugin.slug ) raise ValidationError(export_error) @@ -367,7 +365,7 @@ class DataExportViewMixin: ) except Exception: - InvenTree.exceptions.log_error(f'plugins.{export_plugin.slug}.export_data') + InvenTree.exceptions.log_error('export_data', plugin=export_plugin.slug) raise ValidationError(export_error) if not isinstance(data, list): @@ -381,7 +379,7 @@ class DataExportViewMixin: headers = export_plugin.update_headers(headers, export_context) except Exception: InvenTree.exceptions.log_error( - f'plugins.{export_plugin.slug}.update_headers' + 'update_headers', plugin=export_plugin.slug ) raise ValidationError(export_error) @@ -389,7 +387,7 @@ class DataExportViewMixin: try: datafile = serializer.export_to_file(data, headers, export_format) except Exception: - InvenTree.exceptions.log_error('export_to_file') + InvenTree.exceptions.log_error('export_to_file', plugin=export_plugin.slug) raise ValidationError(_('Error occurred during data export')) # Update the output object with the exported data diff --git a/src/backend/InvenTree/data_exporter/serializers.py b/src/backend/InvenTree/data_exporter/serializers.py index d5f90fe48f..2039b79521 100644 --- a/src/backend/InvenTree/data_exporter/serializers.py +++ b/src/backend/InvenTree/data_exporter/serializers.py @@ -58,7 +58,7 @@ class DataExportOptionsSerializer(serializers.Serializer): view_class=view_class, ) except Exception: - InvenTree.exceptions.log_error(f'plugin.{plugin.slug}.supports_export') + InvenTree.exceptions.log_error('supports_export', plugin=plugin.slug) supports_export = False if supports_export: diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 81f673daca..a74f541313 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -657,7 +657,7 @@ class Part( if raise_error: raise ValidationError({'name': exc.message}) except Exception: - log_error(f'{plugin.slug}.validate_part_name') + log_error('validate_part_name', plugin=plugin.slug) def validate_ipn(self, raise_error=True): """Ensure that the IPN (internal part number) is valid for this Part". @@ -678,7 +678,7 @@ class Part( if raise_error: raise ValidationError({'IPN': exc.message}) except Exception: - log_error(f'{plugin.slug}.validate_part_ipn') + log_error('validate_part_ipn', plugin=plugin.slug) # If we get to here, none of the plugins have raised an error pattern = get_global_setting('PART_IPN_REGEX', '', create=False).strip() @@ -767,11 +767,11 @@ class Part( # First, throw the serial number against each of the loaded validation plugins from plugin import PluginMixinEnum, registry - try: - for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): - # Run the serial number through each custom validator - # If the plugin returns 'True' we will skip any subsequent validation + for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): + # Run the serial number through each custom validator + # If the plugin returns 'True' we will skip any subsequent validation + try: result = False if hasattr(plugin, 'validate_serial_number'): @@ -788,14 +788,14 @@ class Part( if result is True: return True - except ValidationError as exc: - if raise_error: - # Re-throw the error - raise exc - else: - return False - except Exception: - log_error('part.validate_serial_number') + except ValidationError as exc: + if raise_error: + # Re-throw the error + raise exc + else: + return False + except Exception: + log_error('validate_serial_number', plugin=plugin.slug) """ If we are here, none of the loaded plugins (if any) threw an error or exited early @@ -896,7 +896,7 @@ class Part( if result is not None: return str(result) except Exception: - log_error(f'{plugin.slug}.get_latest_serial_number') + log_error('get_latest_serial_number', plugin=plugin.slug) # No plugin returned a result, so we will run the default query stock = ( @@ -3991,7 +3991,7 @@ class PartParameter(InvenTree.models.InvenTreeMetadataModel): # Re-throw the ValidationError against the 'data' field raise ValidationError({'data': exc.message}) except Exception: - log_error(f'{plugin.slug}.validate_part_parameter') + log_error('validate_part_parameter', plugin=plugin.slug) def calculate_numeric_value(self): """Calculate a numeric value for the parameter data. diff --git a/src/backend/InvenTree/plugin/base/action/api.py b/src/backend/InvenTree/plugin/base/action/api.py index e50dd90285..1cfdb57964 100644 --- a/src/backend/InvenTree/plugin/base/action/api.py +++ b/src/backend/InvenTree/plugin/base/action/api.py @@ -40,7 +40,7 @@ class ActionPluginView(GenericAPIView): plugin.perform_action(request.user, data=data) return Response(plugin.get_response(request.user, data=data)) except Exception: - log_error('action_plugin') + log_error('perform_action', plugin=plugin.slug) # If we got to here, no matching action was found return Response({'error': _('No matching action found'), 'action': action}) diff --git a/src/backend/InvenTree/plugin/base/barcodes/api.py b/src/backend/InvenTree/plugin/base/barcodes/api.py index 301667c509..4e9e72d04d 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/api.py +++ b/src/backend/InvenTree/plugin/base/barcodes/api.py @@ -154,7 +154,7 @@ class BarcodeView(CreateAPIView): try: result = current_plugin.scan(barcode) except Exception: - log_error('BarcodeView.scan_barcode') + log_error('BarcodeView.scan_barcode', plugin=current_plugin.slug) continue if result is None: @@ -546,7 +546,7 @@ class BarcodePOReceive(BarcodeView): auto_allocate=auto_allocate, ) except Exception: - log_error('BarcodePOReceive.handle_barcode') + log_error('BarcodePOReceive.handle_barcode', plugin=current_plugin.slug) continue if result is None: diff --git a/src/backend/InvenTree/plugin/base/event/events.py b/src/backend/InvenTree/plugin/base/event/events.py index 01aa91cc93..ec36a1928d 100644 --- a/src/backend/InvenTree/plugin/base/event/events.py +++ b/src/backend/InvenTree/plugin/base/event/events.py @@ -111,7 +111,7 @@ def process_event(plugin_slug, event, *args, **kwargs): plugin.process_event(event, *args, **kwargs) except Exception as e: # Log the exception to the database - InvenTree.exceptions.log_error(f'plugins.{plugin_slug}.process_event') + InvenTree.exceptions.log_error('process_event', plugin=plugin_slug) # Re-throw the exception so that the background worker tries again raise e diff --git a/src/backend/InvenTree/plugin/base/label/label.py b/src/backend/InvenTree/plugin/base/label/label.py index 05a28a9a1b..028c3c8e09 100644 --- a/src/backend/InvenTree/plugin/base/label/label.py +++ b/src/backend/InvenTree/plugin/base/label/label.py @@ -42,7 +42,7 @@ def print_label(plugin_slug: str, **kwargs): if user: # Log an error message to the database - log_error('plugin.print_label') + log_error('print_label', plugin=plugin.slug) logger.exception( "Label printing failed: Sending notification to user '%s'", user ) # pragma: no cover diff --git a/src/backend/InvenTree/plugin/base/locate/api.py b/src/backend/InvenTree/plugin/base/locate/api.py index e33ca2f0b2..406884c465 100644 --- a/src/backend/InvenTree/plugin/base/locate/api.py +++ b/src/backend/InvenTree/plugin/base/locate/api.py @@ -76,7 +76,7 @@ class LocatePluginView(GenericAPIView): except (ValueError, StockItem.DoesNotExist): raise NotFound(f"StockItem matching PK '{item_pk}' not found") except Exception: - log_error('locate_stock_item') + log_error('locate_stock_item', plugin=plugin.slug) return ValidationError('Error locating stock item') elif location_pk: @@ -98,7 +98,7 @@ class LocatePluginView(GenericAPIView): except (ValueError, StockLocation.DoesNotExist): raise NotFound(f"StockLocation matching PK '{location_pk}' not found") except Exception: - log_error('locate_stock_location') + log_error('locate_stock_location', plugin=plugin.slug) return ValidationError('Error locating stock location') else: raise ParseError("Must supply either 'item' or 'location' parameter") diff --git a/src/backend/InvenTree/plugin/base/ui/api.py b/src/backend/InvenTree/plugin/base/ui/api.py index 2307e02a30..05c7306c76 100644 --- a/src/backend/InvenTree/plugin/base/ui/api.py +++ b/src/backend/InvenTree/plugin/base/ui/api.py @@ -40,7 +40,7 @@ class PluginUIFeatureList(APIView): except Exception: # Custom features could not load for this plugin # Log the error and continue - log_error(f'{_plugin.slug}.get_ui_features') + log_error('get_ui_features', plugin=_plugin.slug) continue if plugin_features and type(plugin_features) is list: @@ -65,7 +65,7 @@ class PluginUIFeatureList(APIView): except Exception: # Custom features could not load # Log the error and continue - log_error(f'{_plugin.slug}.get_ui_features') + log_error('get_ui_features', plugin=_plugin.slug) continue return Response(features) diff --git a/src/backend/InvenTree/report/models.py b/src/backend/InvenTree/report/models.py index 8cc046c91d..42c0273e3e 100644 --- a/src/backend/InvenTree/report/models.py +++ b/src/backend/InvenTree/report/models.py @@ -403,9 +403,7 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase): try: plugin.add_report_context(self, instance, request, context) except Exception: - InvenTree.exceptions.log_error( - f'plugins.{plugin.slug}.add_report_context' - ) + InvenTree.exceptions.log_error('add_report_context', plugin=plugin.slug) return context @@ -495,7 +493,7 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase): plugin.report_callback(self, instance, report, request) except Exception: InvenTree.exceptions.log_error( - f'plugins.{plugin.slug}.report_callback' + 'report_callback', plugin=plugin.slug ) # Update the progress of the report generation @@ -617,9 +615,7 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase): try: plugin.add_label_context(self, instance, request, context) except Exception: - InvenTree.exceptions.log_error( - f'plugins.{plugin.slug}.add_label_context' - ) + InvenTree.exceptions.log_error('add_label_context', plugin=plugin.slug) return context @@ -684,7 +680,7 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase): raise e except Exception as e: output.delete() - InvenTree.exceptions.log_error(f'plugins.{plugin.slug}.print_labels') + InvenTree.exceptions.log_error('print_labels', plugin=plugin.slug) raise ValidationError([_('Error printing labels'), str(e)]) output.refresh_from_db() diff --git a/src/backend/InvenTree/stock/generators.py b/src/backend/InvenTree/stock/generators.py index 7b11dc7db8..a52f4518ed 100644 --- a/src/backend/InvenTree/stock/generators.py +++ b/src/backend/InvenTree/stock/generators.py @@ -52,14 +52,18 @@ def generate_batch_code(**kwargs): try: batch = generate(**context) except Exception: - InvenTree.exceptions.log_error('plugin.generate_batch_code') + InvenTree.exceptions.log_error( + 'generate_batch_code', plugin=plugin.slug + ) continue else: # Ignore the kwargs (legacy plugin) try: batch = generate() except Exception: - InvenTree.exceptions.log_error('plugin.generate_batch_code') + InvenTree.exceptions.log_error( + 'generate_batch_code', plugin=plugin.slug + ) continue # Return the first non-null value generated by a plugin diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 168a684eaa..b0a4e1b63a 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -608,7 +608,7 @@ class StockItem( serial_int = plugin.convert_serial_to_int(serial) except Exception: InvenTree.exceptions.log_error( - f'plugin.{plugin.slug}.convert_serial_to_int' + 'convert_serial_to_int', plugin=plugin.slug ) serial_int = None @@ -808,7 +808,7 @@ class StockItem( raise ValidationError({'batch': exc.message}) except Exception: InvenTree.exceptions.log_error( - f'plugin.{plugin.slug}.validate_batch_code' + 'validate_batch_code', plugin=plugin.slug ) def clean(self):