2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-14 19:15:41 +00:00

Refactor error logging (#9736)

- Ensure plugin slug is correctly attached
- Consistent format
- Logic fixes
This commit is contained in:
Oliver
2025-06-04 11:36:47 +10:00
committed by GitHub
parent 5513291d3f
commit ee67c4f099
16 changed files with 60 additions and 50 deletions

View File

@ -4,6 +4,7 @@
import sys import sys
import traceback import traceback
from typing import Optional
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
@ -17,7 +18,13 @@ from rest_framework.response import Response
logger = structlog.get_logger('inventree') 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. """Log an error to the database.
- Uses python exception handling to extract error details - 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_name: The name of the error (optional, overrides 'kind')
error_info: The error information (optional, overrides 'info') error_info: The error information (optional, overrides 'info')
error_data: The error data (optional, overrides 'data') error_data: The error data (optional, overrides 'data')
plugin: The plugin name associated with this error (optional)
""" """
from error_report.models import Error 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 # Log error to stderr
logger.error(info) 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 # Ensure the error information does not exceed field size limits
path = path[:200] path = path[:200]
kind = kind[:128] kind = kind[:128]

View File

@ -510,7 +510,7 @@ def increment_serial_number(serial, part=None):
if result is not None: if result is not None:
return str(result) return str(result)
except Exception: 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 # If we get to here, no plugins were able to "increment" the provided serial value
# Attempt to perform increment according to some basic rules # Attempt to perform increment according to some basic rules

View File

@ -102,7 +102,7 @@ class PluginValidationMixin(DiffMixin):
import InvenTree.exceptions import InvenTree.exceptions
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error(
f'plugins.{plugin.slug}.validate_model_instance' 'validate_model_instance', plugin=plugin.slug
) )
raise ValidationError(_('Error running plugin validation')) raise ValidationError(_('Error running plugin validation'))
@ -139,7 +139,7 @@ class PluginValidationMixin(DiffMixin):
# Plugin might raise a ValidationError to prevent deletion # Plugin might raise a ValidationError to prevent deletion
raise e raise e
except Exception: except Exception:
log_error('plugin.validate_model_deletion') log_error('validate_model_deletion', plugin=plugin.slug)
continue continue
super().delete() super().delete()

View File

@ -79,7 +79,7 @@ def get_icon_packs():
try: try:
icon_packs.extend(plugin.icon_packs()) icon_packs.extend(plugin.icon_packs())
except Exception as e: 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) logger.warning('Error loading icon pack from plugin %s: %s', plugin, e)
_icon_packs = {pack.prefix: pack for pack in icon_packs} _icon_packs = {pack.prefix: pack for pack in icon_packs}

View File

@ -332,9 +332,7 @@ class DataExportViewMixin:
try: try:
queryset = export_plugin.filter_queryset(queryset) queryset = export_plugin.filter_queryset(queryset)
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error('filter_queryset', plugin=export_plugin.slug)
f'plugins.{export_plugin.slug}.filter_queryset'
)
raise ValidationError(export_error) raise ValidationError(export_error)
# Update the output instance with the total number of items to export # Update the output instance with the total number of items to export
@ -355,7 +353,7 @@ class DataExportViewMixin:
) )
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error(
f'plugins.{export_plugin.slug}.generate_filename' 'generate_filename', plugin=export_plugin.slug
) )
raise ValidationError(export_error) raise ValidationError(export_error)
@ -367,7 +365,7 @@ class DataExportViewMixin:
) )
except Exception: 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) raise ValidationError(export_error)
if not isinstance(data, list): if not isinstance(data, list):
@ -381,7 +379,7 @@ class DataExportViewMixin:
headers = export_plugin.update_headers(headers, export_context) headers = export_plugin.update_headers(headers, export_context)
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error(
f'plugins.{export_plugin.slug}.update_headers' 'update_headers', plugin=export_plugin.slug
) )
raise ValidationError(export_error) raise ValidationError(export_error)
@ -389,7 +387,7 @@ class DataExportViewMixin:
try: try:
datafile = serializer.export_to_file(data, headers, export_format) datafile = serializer.export_to_file(data, headers, export_format)
except Exception: 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')) raise ValidationError(_('Error occurred during data export'))
# Update the output object with the exported data # Update the output object with the exported data

View File

@ -58,7 +58,7 @@ class DataExportOptionsSerializer(serializers.Serializer):
view_class=view_class, view_class=view_class,
) )
except Exception: except Exception:
InvenTree.exceptions.log_error(f'plugin.{plugin.slug}.supports_export') InvenTree.exceptions.log_error('supports_export', plugin=plugin.slug)
supports_export = False supports_export = False
if supports_export: if supports_export:

View File

@ -657,7 +657,7 @@ class Part(
if raise_error: if raise_error:
raise ValidationError({'name': exc.message}) raise ValidationError({'name': exc.message})
except Exception: 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): def validate_ipn(self, raise_error=True):
"""Ensure that the IPN (internal part number) is valid for this Part". """Ensure that the IPN (internal part number) is valid for this Part".
@ -678,7 +678,7 @@ class Part(
if raise_error: if raise_error:
raise ValidationError({'IPN': exc.message}) raise ValidationError({'IPN': exc.message})
except Exception: 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 # If we get to here, none of the plugins have raised an error
pattern = get_global_setting('PART_IPN_REGEX', '', create=False).strip() 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 # First, throw the serial number against each of the loaded validation plugins
from plugin import PluginMixinEnum, registry from plugin import PluginMixinEnum, registry
try: for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION):
for plugin in registry.with_mixin(PluginMixinEnum.VALIDATION): # Run the serial number through each custom validator
# Run the serial number through each custom validator # If the plugin returns 'True' we will skip any subsequent validation
# If the plugin returns 'True' we will skip any subsequent validation
try:
result = False result = False
if hasattr(plugin, 'validate_serial_number'): if hasattr(plugin, 'validate_serial_number'):
@ -788,14 +788,14 @@ class Part(
if result is True: if result is True:
return True return True
except ValidationError as exc: except ValidationError as exc:
if raise_error: if raise_error:
# Re-throw the error # Re-throw the error
raise exc raise exc
else: else:
return False return False
except Exception: except Exception:
log_error('part.validate_serial_number') 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 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: if result is not None:
return str(result) return str(result)
except Exception: 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 # No plugin returned a result, so we will run the default query
stock = ( stock = (
@ -3991,7 +3991,7 @@ class PartParameter(InvenTree.models.InvenTreeMetadataModel):
# Re-throw the ValidationError against the 'data' field # Re-throw the ValidationError against the 'data' field
raise ValidationError({'data': exc.message}) raise ValidationError({'data': exc.message})
except Exception: except Exception:
log_error(f'{plugin.slug}.validate_part_parameter') log_error('validate_part_parameter', plugin=plugin.slug)
def calculate_numeric_value(self): def calculate_numeric_value(self):
"""Calculate a numeric value for the parameter data. """Calculate a numeric value for the parameter data.

View File

@ -40,7 +40,7 @@ class ActionPluginView(GenericAPIView):
plugin.perform_action(request.user, data=data) plugin.perform_action(request.user, data=data)
return Response(plugin.get_response(request.user, data=data)) return Response(plugin.get_response(request.user, data=data))
except Exception: except Exception:
log_error('action_plugin') log_error('perform_action', plugin=plugin.slug)
# If we got to here, no matching action was found # If we got to here, no matching action was found
return Response({'error': _('No matching action found'), 'action': action}) return Response({'error': _('No matching action found'), 'action': action})

View File

@ -154,7 +154,7 @@ class BarcodeView(CreateAPIView):
try: try:
result = current_plugin.scan(barcode) result = current_plugin.scan(barcode)
except Exception: except Exception:
log_error('BarcodeView.scan_barcode') log_error('BarcodeView.scan_barcode', plugin=current_plugin.slug)
continue continue
if result is None: if result is None:
@ -546,7 +546,7 @@ class BarcodePOReceive(BarcodeView):
auto_allocate=auto_allocate, auto_allocate=auto_allocate,
) )
except Exception: except Exception:
log_error('BarcodePOReceive.handle_barcode') log_error('BarcodePOReceive.handle_barcode', plugin=current_plugin.slug)
continue continue
if result is None: if result is None:

View File

@ -111,7 +111,7 @@ def process_event(plugin_slug, event, *args, **kwargs):
plugin.process_event(event, *args, **kwargs) plugin.process_event(event, *args, **kwargs)
except Exception as e: except Exception as e:
# Log the exception to the database # 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 # Re-throw the exception so that the background worker tries again
raise e raise e

View File

@ -42,7 +42,7 @@ def print_label(plugin_slug: str, **kwargs):
if user: if user:
# Log an error message to the database # Log an error message to the database
log_error('plugin.print_label') log_error('print_label', plugin=plugin.slug)
logger.exception( logger.exception(
"Label printing failed: Sending notification to user '%s'", user "Label printing failed: Sending notification to user '%s'", user
) # pragma: no cover ) # pragma: no cover

View File

@ -76,7 +76,7 @@ class LocatePluginView(GenericAPIView):
except (ValueError, StockItem.DoesNotExist): except (ValueError, StockItem.DoesNotExist):
raise NotFound(f"StockItem matching PK '{item_pk}' not found") raise NotFound(f"StockItem matching PK '{item_pk}' not found")
except Exception: except Exception:
log_error('locate_stock_item') log_error('locate_stock_item', plugin=plugin.slug)
return ValidationError('Error locating stock item') return ValidationError('Error locating stock item')
elif location_pk: elif location_pk:
@ -98,7 +98,7 @@ class LocatePluginView(GenericAPIView):
except (ValueError, StockLocation.DoesNotExist): except (ValueError, StockLocation.DoesNotExist):
raise NotFound(f"StockLocation matching PK '{location_pk}' not found") raise NotFound(f"StockLocation matching PK '{location_pk}' not found")
except Exception: except Exception:
log_error('locate_stock_location') log_error('locate_stock_location', plugin=plugin.slug)
return ValidationError('Error locating stock location') return ValidationError('Error locating stock location')
else: else:
raise ParseError("Must supply either 'item' or 'location' parameter") raise ParseError("Must supply either 'item' or 'location' parameter")

View File

@ -40,7 +40,7 @@ class PluginUIFeatureList(APIView):
except Exception: except Exception:
# Custom features could not load for this plugin # Custom features could not load for this plugin
# Log the error and continue # Log the error and continue
log_error(f'{_plugin.slug}.get_ui_features') log_error('get_ui_features', plugin=_plugin.slug)
continue continue
if plugin_features and type(plugin_features) is list: if plugin_features and type(plugin_features) is list:
@ -65,7 +65,7 @@ class PluginUIFeatureList(APIView):
except Exception: except Exception:
# Custom features could not load # Custom features could not load
# Log the error and continue # Log the error and continue
log_error(f'{_plugin.slug}.get_ui_features') log_error('get_ui_features', plugin=_plugin.slug)
continue continue
return Response(features) return Response(features)

View File

@ -403,9 +403,7 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
try: try:
plugin.add_report_context(self, instance, request, context) plugin.add_report_context(self, instance, request, context)
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error('add_report_context', plugin=plugin.slug)
f'plugins.{plugin.slug}.add_report_context'
)
return context return context
@ -495,7 +493,7 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
plugin.report_callback(self, instance, report, request) plugin.report_callback(self, instance, report, request)
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error(
f'plugins.{plugin.slug}.report_callback' 'report_callback', plugin=plugin.slug
) )
# Update the progress of the report generation # Update the progress of the report generation
@ -617,9 +615,7 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase):
try: try:
plugin.add_label_context(self, instance, request, context) plugin.add_label_context(self, instance, request, context)
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error('add_label_context', plugin=plugin.slug)
f'plugins.{plugin.slug}.add_label_context'
)
return context return context
@ -684,7 +680,7 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase):
raise e raise e
except Exception as e: except Exception as e:
output.delete() 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)]) raise ValidationError([_('Error printing labels'), str(e)])
output.refresh_from_db() output.refresh_from_db()

View File

@ -52,14 +52,18 @@ def generate_batch_code(**kwargs):
try: try:
batch = generate(**context) batch = generate(**context)
except Exception: except Exception:
InvenTree.exceptions.log_error('plugin.generate_batch_code') InvenTree.exceptions.log_error(
'generate_batch_code', plugin=plugin.slug
)
continue continue
else: else:
# Ignore the kwargs (legacy plugin) # Ignore the kwargs (legacy plugin)
try: try:
batch = generate() batch = generate()
except Exception: except Exception:
InvenTree.exceptions.log_error('plugin.generate_batch_code') InvenTree.exceptions.log_error(
'generate_batch_code', plugin=plugin.slug
)
continue continue
# Return the first non-null value generated by a plugin # Return the first non-null value generated by a plugin

View File

@ -608,7 +608,7 @@ class StockItem(
serial_int = plugin.convert_serial_to_int(serial) serial_int = plugin.convert_serial_to_int(serial)
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error(
f'plugin.{plugin.slug}.convert_serial_to_int' 'convert_serial_to_int', plugin=plugin.slug
) )
serial_int = None serial_int = None
@ -808,7 +808,7 @@ class StockItem(
raise ValidationError({'batch': exc.message}) raise ValidationError({'batch': exc.message})
except Exception: except Exception:
InvenTree.exceptions.log_error( InvenTree.exceptions.log_error(
f'plugin.{plugin.slug}.validate_batch_code' 'validate_batch_code', plugin=plugin.slug
) )
def clean(self): def clean(self):