2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-13 18:45:40 +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 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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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