569 lines
No EOL
26 KiB
Python
569 lines
No EOL
26 KiB
Python
import os
|
|
import json
|
|
from datetime import datetime
|
|
import logging
|
|
import traceback
|
|
from typing import Dict, Any, Optional
|
|
|
|
class HTMLErrorReporter:
|
|
"""
|
|
Generates user-friendly HTML error reports when QC processing fails.
|
|
Provides actionable error messages and fix instructions for users.
|
|
"""
|
|
|
|
@staticmethod
|
|
def generate_error_report(
|
|
error_type: str,
|
|
error_message: str,
|
|
filename: str,
|
|
reports_dir: str,
|
|
error_details: Optional[Dict[str, Any]] = None,
|
|
exception_info: Optional[str] = None
|
|
) -> str:
|
|
"""
|
|
Generate an HTML error report.
|
|
|
|
:param error_type: Type of error (e.g., 'zip_extraction', 'json_parsing', 'file_access')
|
|
:param error_message: Human-readable error message
|
|
:param filename: Name of the file that caused the error
|
|
:param reports_dir: Directory to save the error report
|
|
:param error_details: Additional error details dictionary
|
|
:param exception_info: Exception traceback information
|
|
:return: Path to the generated error report
|
|
"""
|
|
try:
|
|
os.makedirs(reports_dir, exist_ok=True)
|
|
timestamp = datetime.now()
|
|
date_str = timestamp.isoformat(timespec='seconds').replace(":", "-")
|
|
safe_name = os.path.basename(filename).replace('.zip', '').replace('.', '_')
|
|
output_path = os.path.join(reports_dir, f"{safe_name}_{date_str}_ERROR.html")
|
|
|
|
html_content = HTMLErrorReporter._build_error_html(
|
|
error_type=error_type,
|
|
error_message=error_message,
|
|
filename=filename,
|
|
timestamp=timestamp,
|
|
error_details=error_details or {},
|
|
exception_info=exception_info
|
|
)
|
|
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
f.write(html_content)
|
|
|
|
logging.info(f"Generated HTML error report: {output_path}")
|
|
return output_path
|
|
|
|
except Exception as e:
|
|
logging.exception("Critical error generating HTML error report")
|
|
# Fallback to text error report if HTML generation fails
|
|
fallback_path = os.path.join(reports_dir, f"{safe_name}_{date_str}_ERROR.txt")
|
|
try:
|
|
with open(fallback_path, 'w', encoding='utf-8') as f:
|
|
f.write(f"Error Type: {error_type}\n")
|
|
f.write(f"Error Message: {error_message}\n")
|
|
f.write(f"Filename: {filename}\n")
|
|
f.write(f"Timestamp: {timestamp}\n")
|
|
if exception_info:
|
|
f.write(f"Exception Info:\n{exception_info}\n")
|
|
return fallback_path
|
|
except Exception:
|
|
logging.exception("Failed to create fallback text error report")
|
|
raise RuntimeError(f"Critical failure: Unable to generate any error report for {filename}")
|
|
|
|
@staticmethod
|
|
def _build_error_html(
|
|
error_type: str,
|
|
error_message: str,
|
|
filename: str,
|
|
timestamp: datetime,
|
|
error_details: Dict[str, Any],
|
|
exception_info: Optional[str]
|
|
) -> str:
|
|
"""Build complete HTML error report."""
|
|
|
|
# Get error-specific content
|
|
error_info = HTMLErrorReporter._get_error_info(error_type, error_message, error_details)
|
|
|
|
return f'''
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
{HTMLErrorReporter._build_head(filename)}
|
|
<body>
|
|
<div class="container py-4">
|
|
{HTMLErrorReporter._build_error_header(filename, timestamp, error_type)}
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="alert alert-danger">
|
|
<h4 class="alert-heading"><i class="fas fa-exclamation-triangle"></i> Processing Failed</h4>
|
|
<p class="mb-0">{error_message}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card border-warning">
|
|
<div class="card-header bg-warning text-dark">
|
|
<h5 class="mb-0"><i class="fas fa-tools"></i> How to Fix This Issue</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{error_info['fix_instructions']}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card border-info">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0"><i class="fas fa-upload"></i> Re-upload Instructions</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{error_info['reupload_instructions']}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion mb-4" id="errorDetails">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="headingDetails">
|
|
<button class="accordion-button collapsed" type="button"
|
|
data-bs-toggle="collapse" data-bs-target="#collapseDetails"
|
|
aria-expanded="false" aria-controls="collapseDetails">
|
|
<i class="fas fa-info-circle me-2"></i> Technical Details
|
|
</button>
|
|
</h2>
|
|
<div id="collapseDetails" class="accordion-collapse collapse"
|
|
aria-labelledby="headingDetails" data-bs-parent="#errorDetails">
|
|
<div class="accordion-body">
|
|
<div class="mb-3">
|
|
<h6>Error Type:</h6>
|
|
<span class="badge bg-danger">{error_type.replace('_', ' ').title()}</span>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<h6>File Information:</h6>
|
|
<ul class="list-unstyled">
|
|
<li><strong>Filename:</strong> {filename}</li>
|
|
<li><strong>Error Time:</strong> {timestamp.strftime('%Y-%m-%d %H:%M:%S')}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
{HTMLErrorReporter._format_error_details(error_details)}
|
|
|
|
{HTMLErrorReporter._format_exception_info(exception_info)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-info">
|
|
<h6><i class="fas fa-question-circle"></i> Need Help?</h6>
|
|
<p class="mb-0">
|
|
If you continue to experience issues after following the fix instructions,
|
|
please contact the technical support team and include this error report.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{HTMLErrorReporter._build_scripts()}
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
@staticmethod
|
|
def _build_head(filename: str) -> str:
|
|
"""Build HTML head section."""
|
|
return f'''
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>QC Error Report - {filename}</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
<style>
|
|
{HTMLErrorReporter._get_css_styles()}
|
|
</style>
|
|
</head>
|
|
'''
|
|
|
|
@staticmethod
|
|
def _get_css_styles() -> str:
|
|
"""Get CSS styles for error reports."""
|
|
return '''
|
|
.error-header {
|
|
background: linear-gradient(135deg, #dc3545, #c82333);
|
|
color: white;
|
|
padding: 1.5rem;
|
|
border-radius: 0.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.error-icon {
|
|
font-size: 3rem;
|
|
opacity: 0.8;
|
|
}
|
|
.fix-instructions ol, .fix-instructions ul {
|
|
padding-left: 1.5rem;
|
|
}
|
|
.fix-instructions li {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
pre {
|
|
background-color: #f8f9fa;
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
font-size: 0.875rem;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
.card {
|
|
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
|
}
|
|
.alert {
|
|
border: none;
|
|
border-radius: 0.5rem;
|
|
}
|
|
'''
|
|
|
|
@staticmethod
|
|
def _build_error_header(filename: str, timestamp: datetime, error_type: str) -> str:
|
|
"""Build error report header."""
|
|
return f'''
|
|
<div class="error-header">
|
|
<div class="row align-items-center">
|
|
<div class="col-auto">
|
|
<i class="fas fa-exclamation-triangle error-icon"></i>
|
|
</div>
|
|
<div class="col">
|
|
<h1 class="h2 mb-1">QC Processing Error</h1>
|
|
<h4 class="mb-2 text-light">{filename}</h4>
|
|
<div class="text-light opacity-75">
|
|
<div>Error Type: {error_type.replace('_', ' ').title()}</div>
|
|
<div>Error Time: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
'''
|
|
|
|
@staticmethod
|
|
def _build_scripts() -> str:
|
|
"""Build JavaScript section."""
|
|
return '''
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
'''
|
|
|
|
@staticmethod
|
|
def _get_error_info(error_type: str, error_message: str, error_details: Dict[str, Any]) -> Dict[str, str]:
|
|
"""Get error-specific fix instructions and re-upload guidance."""
|
|
|
|
error_templates = {
|
|
'zip_extraction': {
|
|
'fix_instructions': '''
|
|
<div class="fix-instructions">
|
|
<p>The ZIP file could not be extracted properly. Common causes and solutions:</p>
|
|
<ol>
|
|
<li><strong>Corrupted ZIP file:</strong> Re-create the ZIP file using a reliable archiver</li>
|
|
<li><strong>Unsupported compression:</strong> Use standard ZIP compression (not RAR, 7z, etc.)</li>
|
|
<li><strong>Password-protected ZIP:</strong> Remove password protection</li>
|
|
<li><strong>Large file size:</strong> Ensure ZIP is under size limits</li>
|
|
<li><strong>Invalid characters:</strong> Avoid special characters in filenames inside ZIP</li>
|
|
</ol>
|
|
<p class="mb-0"><strong>Test your ZIP:</strong> Before uploading, extract it locally to verify it works properly.</p>
|
|
</div>
|
|
''',
|
|
'reupload_instructions': '''
|
|
<div>
|
|
<ol>
|
|
<li>Fix the ZIP file using the instructions provided</li>
|
|
<li>Test the ZIP file by extracting it locally</li>
|
|
<li>Ensure all required files are present (especially linkingrecord.json)</li>
|
|
<li>Re-upload the corrected ZIP file to the input folder</li>
|
|
<li>Monitor for a new QC report within a few minutes</li>
|
|
</ol>
|
|
</div>
|
|
'''
|
|
},
|
|
'json_parsing': {
|
|
'fix_instructions': '''
|
|
<div class="fix-instructions">
|
|
<p>The JSON file (likely linkingrecord.json) contains syntax errors:</p>
|
|
<ol>
|
|
<li><strong>Use a JSON validator:</strong> Copy your JSON content to <a href="https://jsonlint.com/" target="_blank">jsonlint.com</a></li>
|
|
<li><strong>Common issues to check:</strong>
|
|
<ul>
|
|
<li>Missing or extra commas</li>
|
|
<li>Unmatched brackets { } or [ ]</li>
|
|
<li>Use double quotes (") not single quotes (')</li>
|
|
<li>Remove tab characters and control characters</li>
|
|
<li>Ensure proper UTF-8 encoding</li>
|
|
</ul>
|
|
</li>
|
|
<li><strong>Re-export JSON:</strong> If possible, regenerate the JSON from your source system</li>
|
|
</ol>
|
|
</div>
|
|
''',
|
|
'reupload_instructions': '''
|
|
<div>
|
|
<ol>
|
|
<li>Validate and fix the JSON file syntax</li>
|
|
<li>Ensure the JSON file is UTF-8 encoded</li>
|
|
<li>Test the JSON in a validator tool</li>
|
|
<li>Re-create the ZIP file with the corrected JSON</li>
|
|
<li>Upload the new ZIP file</li>
|
|
</ol>
|
|
</div>
|
|
'''
|
|
},
|
|
'file_access': {
|
|
'fix_instructions': '''
|
|
<div class="fix-instructions">
|
|
<p>The file could not be accessed or read:</p>
|
|
<ol>
|
|
<li><strong>File not found:</strong> Verify the file exists and path is correct</li>
|
|
<li><strong>Permission issues:</strong> Check file permissions and access rights</li>
|
|
<li><strong>File in use:</strong> Ensure the file is not locked by another process</li>
|
|
<li><strong>Network issues:</strong> For remote files, check network connectivity</li>
|
|
<li><strong>Disk space:</strong> Ensure sufficient disk space for processing</li>
|
|
</ol>
|
|
</div>
|
|
''',
|
|
'reupload_instructions': '''
|
|
<div>
|
|
<ol>
|
|
<li>Verify the file is accessible and not corrupted</li>
|
|
<li>Check file permissions and ensure it's not locked</li>
|
|
<li>Try uploading the file again</li>
|
|
<li>If using a network location, verify connectivity</li>
|
|
<li>Contact support if the issue persists</li>
|
|
</ol>
|
|
</div>
|
|
'''
|
|
},
|
|
'qc_profile': {
|
|
'fix_instructions': '''
|
|
<div class="fix-instructions">
|
|
<p>There was an issue with the QC configuration profile:</p>
|
|
<ol>
|
|
<li><strong>Profile not found:</strong> Verify the QC profile file exists</li>
|
|
<li><strong>Invalid JSON:</strong> Check profile JSON syntax</li>
|
|
<li><strong>Missing checks:</strong> Ensure all required check modules are available</li>
|
|
<li><strong>Configuration error:</strong> Verify check configurations are valid</li>
|
|
</ol>
|
|
<p class="mb-0"><strong>Note:</strong> This is typically a system configuration issue. Contact technical support.</p>
|
|
</div>
|
|
''',
|
|
'reupload_instructions': '''
|
|
<div>
|
|
<ol>
|
|
<li>This appears to be a system configuration issue</li>
|
|
<li>Please contact technical support</li>
|
|
<li>Include this error report when contacting support</li>
|
|
<li>Do not modify system configuration files</li>
|
|
</ol>
|
|
</div>
|
|
'''
|
|
},
|
|
'check_execution': {
|
|
'fix_instructions': '''
|
|
<div class="fix-instructions">
|
|
<p>An error occurred while running QC checks on your asset pack:</p>
|
|
<ol>
|
|
<li><strong>Missing required files:</strong> Ensure all required files are in the ZIP</li>
|
|
<li><strong>Invalid file formats:</strong> Check image formats and specifications</li>
|
|
<li><strong>Corrupted files:</strong> Verify all files in the pack are not corrupted</li>
|
|
<li><strong>Naming conventions:</strong> Check file and folder naming follows requirements</li>
|
|
<li><strong>File structure:</strong> Ensure directory structure matches requirements</li>
|
|
</ol>
|
|
</div>
|
|
''',
|
|
'reupload_instructions': '''
|
|
<div>
|
|
<ol>
|
|
<li>Review your asset pack contents</li>
|
|
<li>Fix any identified issues with files or structure</li>
|
|
<li>Re-create the ZIP file</li>
|
|
<li>Upload the corrected asset pack</li>
|
|
<li>If errors persist, check technical details below</li>
|
|
</ol>
|
|
</div>
|
|
'''
|
|
},
|
|
'network_upload': {
|
|
'fix_instructions': '''
|
|
<div class="fix-instructions">
|
|
<p>Failed to upload results due to network or connectivity issues:</p>
|
|
<ol>
|
|
<li><strong>Network connectivity:</strong> Check internet connection</li>
|
|
<li><strong>Service availability:</strong> Verify upload service is accessible</li>
|
|
<li><strong>File size limits:</strong> Ensure files are within upload limits</li>
|
|
<li><strong>Permissions:</strong> Check upload permissions and authentication</li>
|
|
</ol>
|
|
<p class="mb-0"><strong>Note:</strong> Your QC processing may have completed successfully, but results couldn't be delivered.</p>
|
|
</div>
|
|
''',
|
|
'reupload_instructions': '''
|
|
<div>
|
|
<ol>
|
|
<li>Check your network connection</li>
|
|
<li>Try uploading your file again</li>
|
|
<li>If the issue persists, contact technical support</li>
|
|
<li>The processing may be working, but results delivery failed</li>
|
|
</ol>
|
|
</div>
|
|
'''
|
|
},
|
|
'unknown': {
|
|
'fix_instructions': '''
|
|
<div class="fix-instructions">
|
|
<p>An unexpected error occurred during processing:</p>
|
|
<ol>
|
|
<li><strong>Try again:</strong> Sometimes temporary issues resolve themselves</li>
|
|
<li><strong>Check file integrity:</strong> Ensure your ZIP file is not corrupted</li>
|
|
<li><strong>Verify contents:</strong> Make sure all required files are present</li>
|
|
<li><strong>Contact support:</strong> If the issue persists, technical support can help</li>
|
|
</ol>
|
|
</div>
|
|
''',
|
|
'reupload_instructions': '''
|
|
<div>
|
|
<ol>
|
|
<li>Wait a few minutes and try uploading again</li>
|
|
<li>If the error persists, contact technical support</li>
|
|
<li>Include this error report when contacting support</li>
|
|
<li>Provide details about your asset pack contents</li>
|
|
</ol>
|
|
</div>
|
|
'''
|
|
}
|
|
}
|
|
|
|
return error_templates.get(error_type, error_templates['unknown'])
|
|
|
|
@staticmethod
|
|
def _format_error_details(error_details: Dict[str, Any]) -> str:
|
|
"""Format additional error details."""
|
|
if not error_details:
|
|
return ""
|
|
|
|
formatted_details = []
|
|
for key, value in error_details.items():
|
|
if isinstance(value, (dict, list)):
|
|
formatted_details.append(f"<li><strong>{key}:</strong><pre>{json.dumps(value, indent=2)}</pre></li>")
|
|
else:
|
|
formatted_details.append(f"<li><strong>{key}:</strong> {value}</li>")
|
|
|
|
if formatted_details:
|
|
return f'''
|
|
<div class="mb-3">
|
|
<h6>Additional Details:</h6>
|
|
<ul class="list-unstyled">
|
|
{''.join(formatted_details)}
|
|
</ul>
|
|
</div>
|
|
'''
|
|
return ""
|
|
|
|
@staticmethod
|
|
def _format_exception_info(exception_info: Optional[str]) -> str:
|
|
"""Format exception traceback information."""
|
|
if not exception_info:
|
|
return ""
|
|
|
|
return f'''
|
|
<div class="mb-3">
|
|
<h6>Exception Information:</h6>
|
|
<pre>{exception_info}</pre>
|
|
</div>
|
|
'''
|
|
|
|
|
|
# Convenience functions for common error types
|
|
def generate_zip_error_report(filename: str, reports_dir: str, error_message: str, exception: Exception = None) -> str:
|
|
"""Generate error report for ZIP extraction failures."""
|
|
exception_info = traceback.format_exc() if exception else None
|
|
return HTMLErrorReporter.generate_error_report(
|
|
error_type='zip_extraction',
|
|
error_message=error_message,
|
|
filename=filename,
|
|
reports_dir=reports_dir,
|
|
exception_info=exception_info
|
|
)
|
|
|
|
def generate_json_error_report(filename: str, reports_dir: str, error_message: str, json_file: str = None, exception: Exception = None) -> str:
|
|
"""Generate error report for JSON parsing failures."""
|
|
error_details = {'json_file': json_file} if json_file else {}
|
|
exception_info = traceback.format_exc() if exception else None
|
|
return HTMLErrorReporter.generate_error_report(
|
|
error_type='json_parsing',
|
|
error_message=error_message,
|
|
filename=filename,
|
|
reports_dir=reports_dir,
|
|
error_details=error_details,
|
|
exception_info=exception_info
|
|
)
|
|
|
|
def generate_file_access_error_report(filename: str, reports_dir: str, error_message: str, file_path: str = None, exception: Exception = None) -> str:
|
|
"""Generate error report for file access failures."""
|
|
error_details = {'attempted_path': file_path} if file_path else {}
|
|
exception_info = traceback.format_exc() if exception else None
|
|
return HTMLErrorReporter.generate_error_report(
|
|
error_type='file_access',
|
|
error_message=error_message,
|
|
filename=filename,
|
|
reports_dir=reports_dir,
|
|
error_details=error_details,
|
|
exception_info=exception_info
|
|
)
|
|
|
|
def generate_qc_check_error_report(filename: str, reports_dir: str, error_message: str, failed_check: str = None, exception: Exception = None) -> str:
|
|
"""Generate error report for QC check execution failures."""
|
|
error_details = {'failed_check': failed_check} if failed_check else {}
|
|
exception_info = traceback.format_exc() if exception else None
|
|
return HTMLErrorReporter.generate_error_report(
|
|
error_type='check_execution',
|
|
error_message=error_message,
|
|
filename=filename,
|
|
reports_dir=reports_dir,
|
|
error_details=error_details,
|
|
exception_info=exception_info
|
|
)
|
|
|
|
def generate_network_error_report(filename: str, reports_dir: str, error_message: str, operation: str = None, exception: Exception = None) -> str:
|
|
"""Generate error report for network/upload failures."""
|
|
error_details = {'failed_operation': operation} if operation else {}
|
|
exception_info = traceback.format_exc() if exception else None
|
|
return HTMLErrorReporter.generate_error_report(
|
|
error_type='network_upload',
|
|
error_message=error_message,
|
|
filename=filename,
|
|
reports_dir=reports_dir,
|
|
error_details=error_details,
|
|
exception_info=exception_info
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Test the error reporter
|
|
import sys
|
|
if len(sys.argv) != 4:
|
|
print("Usage: python html_error_reporter.py <error_type> <filename> <output_dir>")
|
|
print("Error types: zip_extraction, json_parsing, file_access, qc_profile, check_execution, network_upload")
|
|
sys.exit(1)
|
|
|
|
error_type = sys.argv[1]
|
|
filename = sys.argv[2]
|
|
output_dir = sys.argv[3]
|
|
|
|
try:
|
|
report_path = HTMLErrorReporter.generate_error_report(
|
|
error_type=error_type,
|
|
error_message=f"Test error message for {error_type}",
|
|
filename=filename,
|
|
reports_dir=output_dir,
|
|
error_details={'test_detail': 'This is a test error report'},
|
|
exception_info="Test exception traceback information"
|
|
)
|
|
print(f"Generated test error report: {report_path}")
|
|
except Exception as e:
|
|
print(f"Error generating test report: {e}")
|
|
sys.exit(1) |