|
|
import logging
|
|
|
import traceback
|
|
|
from datetime import datetime
|
|
|
import json
|
|
|
from pathlib import Path
|
|
|
from typing import Dict, Any, Optional
|
|
|
|
|
|
|
|
|
logging.basicConfig(
|
|
|
level=logging.INFO,
|
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
|
handlers=[
|
|
|
logging.FileHandler('api_errors.log'),
|
|
|
logging.StreamHandler()
|
|
|
]
|
|
|
)
|
|
|
|
|
|
logger = logging.getLogger("api.errors")
|
|
|
|
|
|
class ErrorLogger:
|
|
|
def __init__(self):
|
|
|
self.error_log_file = "detailed_errors.json"
|
|
|
self.errors: Dict[str, Any] = self._load_errors()
|
|
|
|
|
|
def _load_errors(self) -> Dict[str, Any]:
|
|
|
"""Load existing errors from file"""
|
|
|
try:
|
|
|
if Path(self.error_log_file).exists():
|
|
|
with open(self.error_log_file, 'r') as f:
|
|
|
return json.load(f)
|
|
|
except Exception as e:
|
|
|
logger.error(f"Failed to load error log: {e}")
|
|
|
return {}
|
|
|
|
|
|
def _save_errors(self):
|
|
|
"""Save errors to file"""
|
|
|
try:
|
|
|
with open(self.error_log_file, 'w') as f:
|
|
|
json.dump(self.errors, f, indent=2)
|
|
|
except Exception as e:
|
|
|
logger.error(f"Failed to save error log: {e}")
|
|
|
|
|
|
def log_error(self,
|
|
|
error: Exception,
|
|
|
endpoint: str,
|
|
|
request_info: Optional[Dict] = None,
|
|
|
context: Optional[Dict] = None):
|
|
|
"""
|
|
|
Log detailed error information
|
|
|
|
|
|
Args:
|
|
|
error: The exception that occurred
|
|
|
endpoint: The API endpoint where the error occurred
|
|
|
request_info: Dictionary containing request information
|
|
|
context: Additional context about the error
|
|
|
"""
|
|
|
timestamp = datetime.now().isoformat()
|
|
|
error_id = f"{timestamp}-{hash(str(error))}"
|
|
|
|
|
|
error_data = {
|
|
|
"timestamp": timestamp,
|
|
|
"error_type": error.__class__.__name__,
|
|
|
"error_message": str(error),
|
|
|
"endpoint": endpoint,
|
|
|
"traceback": traceback.format_exc(),
|
|
|
"request_info": request_info or {},
|
|
|
"context": context or {}
|
|
|
}
|
|
|
|
|
|
|
|
|
self.errors[error_id] = error_data
|
|
|
|
|
|
|
|
|
self._save_errors()
|
|
|
|
|
|
|
|
|
logger.error(
|
|
|
f"Error in {endpoint}: {error.__class__.__name__} - {str(error)}\n"
|
|
|
f"Context: {json.dumps(context or {}, indent=2)}\n"
|
|
|
f"Request: {json.dumps(request_info or {}, indent=2)}\n"
|
|
|
f"Traceback: {traceback.format_exc()}"
|
|
|
)
|
|
|
|
|
|
return error_id
|
|
|
|
|
|
def get_error(self, error_id: str) -> Optional[Dict]:
|
|
|
"""Retrieve detailed error information by ID"""
|
|
|
return self.errors.get(error_id)
|
|
|
|
|
|
def get_recent_errors(self, limit: int = 10) -> Dict[str, Any]:
|
|
|
"""Get most recent errors"""
|
|
|
sorted_errors = sorted(
|
|
|
self.errors.items(),
|
|
|
key=lambda x: x[1]["timestamp"],
|
|
|
reverse=True
|
|
|
)
|
|
|
return dict(sorted_errors[:limit])
|
|
|
|
|
|
def get_error_summary(self) -> Dict[str, Any]:
|
|
|
"""Get summary of errors by type"""
|
|
|
summary = {}
|
|
|
for error_id, error_data in self.errors.items():
|
|
|
error_type = error_data["error_type"]
|
|
|
if error_type not in summary:
|
|
|
summary[error_type] = {
|
|
|
"count": 0,
|
|
|
"last_occurrence": None,
|
|
|
"endpoints": set()
|
|
|
}
|
|
|
|
|
|
summary[error_type]["count"] += 1
|
|
|
summary[error_type]["endpoints"].add(error_data["endpoint"])
|
|
|
|
|
|
if (not summary[error_type]["last_occurrence"] or
|
|
|
error_data["timestamp"] > summary[error_type]["last_occurrence"]):
|
|
|
summary[error_type]["last_occurrence"] = error_data["timestamp"]
|
|
|
|
|
|
|
|
|
for error_type in summary:
|
|
|
summary[error_type]["endpoints"] = list(summary[error_type]["endpoints"])
|
|
|
|
|
|
return summary |