mirror of
https://github.com/yyhuni/xingrin.git
synced 2026-02-07 15:13:11 +08:00
- Add vulnerability snapshot DTO, handler, repository, and service layer with comprehensive test coverage - Add screenshot snapshot DTO, handler, repository, and service layer for snapshot management - Reorganize directories tab from secondary assets navigation to primary navigation in scan history and target layouts - Update frontend navigation to include FolderSearch icon for directories tab with badge count display - Add i18n translations for directories tab in English and Chinese messages - Implement seed data generation tools with Python API client for testing and data population - Add data generator, error handler, and progress tracking utilities for seed API - Update target validator to support new snapshot-related validations - Refactor organization and vulnerability handlers to support snapshot operations - Add integration tests and property-based tests for vulnerability snapshot functionality - Update Go module dependencies to support new snapshot features
163 lines
5.2 KiB
Python
163 lines
5.2 KiB
Python
"""
|
|
Error Handler Module
|
|
|
|
Handles API errors, retry logic, and error logging.
|
|
"""
|
|
|
|
import time
|
|
from typing import Optional, Callable, Any
|
|
|
|
|
|
class ErrorHandler:
|
|
"""Handles errors and retry logic for API calls."""
|
|
|
|
def __init__(self, max_retries: int = 3, retry_delay: float = 1.0):
|
|
"""
|
|
Initialize error handler.
|
|
|
|
Args:
|
|
max_retries: Maximum number of retries for failed requests
|
|
retry_delay: Delay in seconds between retries
|
|
"""
|
|
self.max_retries = max_retries
|
|
self.retry_delay = retry_delay
|
|
|
|
def should_retry(self, status_code: int) -> bool:
|
|
"""
|
|
Determine if a request should be retried based on status code.
|
|
|
|
Args:
|
|
status_code: HTTP status code
|
|
|
|
Returns:
|
|
True if should retry, False otherwise
|
|
"""
|
|
# Retry on 5xx server errors
|
|
if 500 <= status_code < 600:
|
|
return True
|
|
|
|
# Retry on 429 rate limit
|
|
if status_code == 429:
|
|
return True
|
|
|
|
# Don't retry on 4xx client errors (except 429)
|
|
return False
|
|
|
|
|
|
def handle_error(self, error: Exception, context: dict) -> bool:
|
|
"""
|
|
Handle error and determine if operation should continue.
|
|
|
|
Args:
|
|
error: The exception that occurred
|
|
context: Context information (endpoint, data, etc.)
|
|
|
|
Returns:
|
|
True if should continue, False if should stop
|
|
"""
|
|
from api_client import APIError
|
|
|
|
# Handle API errors
|
|
if isinstance(error, APIError):
|
|
if error.status_code and self.should_retry(error.status_code):
|
|
return True # Continue (will be retried)
|
|
else:
|
|
# Log and skip this record
|
|
self.log_error(str(error), context.get("request_data"), context.get("response_data"))
|
|
return True # Continue with next record
|
|
|
|
# Handle network errors (timeout, connection error)
|
|
if isinstance(error, Exception) and "Network error" in str(error):
|
|
return True # Continue (will be retried)
|
|
|
|
# Unknown error - log and continue
|
|
self.log_error(str(error), context.get("request_data"))
|
|
return True
|
|
|
|
def retry_with_backoff(self, func: Callable, *args, **kwargs) -> Any:
|
|
"""
|
|
Execute function with retry and exponential backoff.
|
|
|
|
Args:
|
|
func: Function to execute
|
|
*args: Positional arguments for function
|
|
**kwargs: Keyword arguments for function
|
|
|
|
Returns:
|
|
Function result
|
|
|
|
Raises:
|
|
Exception: If all retries fail
|
|
"""
|
|
from api_client import APIError
|
|
|
|
last_error = None
|
|
|
|
for attempt in range(self.max_retries + 1):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except APIError as e:
|
|
last_error = e
|
|
|
|
# Don't retry if not a retryable error
|
|
if e.status_code and not self.should_retry(e.status_code):
|
|
raise
|
|
|
|
# Don't retry on last attempt
|
|
if attempt >= self.max_retries:
|
|
raise
|
|
|
|
# Calculate delay (longer for rate limit)
|
|
delay = 5.0 if e.status_code == 429 else self.retry_delay
|
|
|
|
print(f" ⚠ Retry {attempt + 1}/{self.max_retries} after {delay}s (Status: {e.status_code})")
|
|
time.sleep(delay)
|
|
except Exception as e:
|
|
last_error = e
|
|
|
|
# Check if it's a network error
|
|
if "Network error" not in str(e):
|
|
raise
|
|
|
|
# Don't retry on last attempt
|
|
if attempt >= self.max_retries:
|
|
raise
|
|
|
|
print(f" ⚠ Retry {attempt + 1}/{self.max_retries} after {self.retry_delay}s (Network error)")
|
|
time.sleep(self.retry_delay)
|
|
|
|
# Should not reach here, but just in case
|
|
if last_error:
|
|
raise last_error
|
|
|
|
|
|
def log_error(self, error: str, request_data: Optional[dict] = None, response_data: Optional[dict] = None):
|
|
"""
|
|
Log error details to file.
|
|
|
|
Args:
|
|
error: Error message
|
|
request_data: Request data (if available)
|
|
response_data: Response data (if available)
|
|
"""
|
|
import json
|
|
from datetime import datetime
|
|
|
|
log_entry = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"error": error,
|
|
}
|
|
|
|
if request_data:
|
|
log_entry["request"] = request_data
|
|
|
|
if response_data:
|
|
log_entry["response"] = response_data
|
|
|
|
# Append to log file
|
|
try:
|
|
with open("seed_errors.log", "a", encoding="utf-8") as f:
|
|
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
|
|
except Exception as e:
|
|
print(f" ⚠ Failed to write error log: {e}")
|