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:
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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})
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
Reference in New Issue
Block a user