- Migrate settings.py to Pydantic V2 (SettingsConfigDict, validation_alias) - Update config models to use @field_validator with @classmethod - Replace deprecated datetime.utcnow() with datetime.now(timezone.utc) - Migrate FastAPI app from @app.on_event to lifespan context manager - Implement comprehensive rate limiting middleware with: * Endpoint-specific rate limits (login: 5/min, register: 3/min) * IP-based and user-based tracking * Authenticated user multiplier (2x limits) * Bypass paths for health, docs, static, websocket endpoints * Rate limit headers in responses - Add 13 comprehensive tests for rate limiting (all passing) - Update instructions.md to mark completed tasks - Fix asyncio.create_task usage in anime_service.py All 714 tests passing. No deprecation warnings.
228 lines
6.1 KiB
Python
228 lines
6.1 KiB
Python
"""
|
|
Error tracking utilities for Aniworld API.
|
|
|
|
This module provides error tracking, logging, and reporting functionality
|
|
for comprehensive error monitoring and debugging.
|
|
"""
|
|
import logging
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Dict, Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ErrorTracker:
|
|
"""
|
|
Centralized error tracking and management.
|
|
|
|
Collects error metadata and provides insights into error patterns.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize error tracker."""
|
|
self.error_history: list[Dict[str, Any]] = []
|
|
self.max_history_size = 1000
|
|
|
|
def track_error(
|
|
self,
|
|
error_type: str,
|
|
message: str,
|
|
request_path: str,
|
|
request_method: str,
|
|
user_id: Optional[str] = None,
|
|
status_code: int = 500,
|
|
details: Optional[Dict[str, Any]] = None,
|
|
request_id: Optional[str] = None,
|
|
) -> str:
|
|
"""
|
|
Track an error occurrence.
|
|
|
|
Args:
|
|
error_type: Type of error
|
|
message: Error message
|
|
request_path: Request path that caused error
|
|
request_method: HTTP method
|
|
user_id: User ID if available
|
|
status_code: HTTP status code
|
|
details: Additional error details
|
|
request_id: Request ID for correlation
|
|
|
|
Returns:
|
|
Unique error tracking ID
|
|
"""
|
|
error_id = str(uuid.uuid4())
|
|
timestamp = datetime.now(timezone.utc).isoformat()
|
|
|
|
error_entry = {
|
|
"id": error_id,
|
|
"timestamp": timestamp,
|
|
"type": error_type,
|
|
"message": message,
|
|
"request_path": request_path,
|
|
"request_method": request_method,
|
|
"user_id": user_id,
|
|
"status_code": status_code,
|
|
"details": details or {},
|
|
"request_id": request_id,
|
|
}
|
|
|
|
self.error_history.append(error_entry)
|
|
|
|
# Keep history size manageable
|
|
if len(self.error_history) > self.max_history_size:
|
|
self.error_history = self.error_history[-self.max_history_size:]
|
|
|
|
logger.info(
|
|
f"Error tracked: {error_id}",
|
|
extra={
|
|
"error_id": error_id,
|
|
"error_type": error_type,
|
|
"status_code": status_code,
|
|
"request_path": request_path,
|
|
},
|
|
)
|
|
|
|
return error_id
|
|
|
|
def get_error_stats(self) -> Dict[str, Any]:
|
|
"""
|
|
Get error statistics from history.
|
|
|
|
Returns:
|
|
Dictionary containing error statistics
|
|
"""
|
|
if not self.error_history:
|
|
return {"total_errors": 0, "error_types": {}}
|
|
|
|
error_types: Dict[str, int] = {}
|
|
status_codes: Dict[int, int] = {}
|
|
|
|
for error in self.error_history:
|
|
error_type = error["type"]
|
|
error_types[error_type] = error_types.get(error_type, 0) + 1
|
|
|
|
status_code = error["status_code"]
|
|
status_codes[status_code] = status_codes.get(status_code, 0) + 1
|
|
|
|
return {
|
|
"total_errors": len(self.error_history),
|
|
"error_types": error_types,
|
|
"status_codes": status_codes,
|
|
"last_error": (
|
|
self.error_history[-1] if self.error_history else None
|
|
),
|
|
}
|
|
|
|
def get_recent_errors(self, limit: int = 10) -> list[Dict[str, Any]]:
|
|
"""
|
|
Get recent errors.
|
|
|
|
Args:
|
|
limit: Maximum number of errors to return
|
|
|
|
Returns:
|
|
List of recent error entries
|
|
"""
|
|
return self.error_history[-limit:] if self.error_history else []
|
|
|
|
def clear_history(self) -> None:
|
|
"""Clear error history."""
|
|
self.error_history.clear()
|
|
logger.info("Error history cleared")
|
|
|
|
|
|
# Global error tracker instance
|
|
_error_tracker: Optional[ErrorTracker] = None
|
|
|
|
|
|
def get_error_tracker() -> ErrorTracker:
|
|
"""
|
|
Get or create global error tracker instance.
|
|
|
|
Returns:
|
|
ErrorTracker instance
|
|
"""
|
|
global _error_tracker
|
|
if _error_tracker is None:
|
|
_error_tracker = ErrorTracker()
|
|
return _error_tracker
|
|
|
|
|
|
def reset_error_tracker() -> None:
|
|
"""Reset error tracker for testing."""
|
|
global _error_tracker
|
|
_error_tracker = None
|
|
|
|
|
|
class RequestContextManager:
|
|
"""
|
|
Manages request context for error tracking.
|
|
|
|
Stores request metadata for error correlation.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize context manager."""
|
|
self.context_stack: list[Dict[str, Any]] = []
|
|
|
|
def push_context(
|
|
self,
|
|
request_id: str,
|
|
request_path: str,
|
|
request_method: str,
|
|
user_id: Optional[str] = None,
|
|
) -> None:
|
|
"""
|
|
Push request context onto stack.
|
|
|
|
Args:
|
|
request_id: Unique request identifier
|
|
request_path: Request path
|
|
request_method: HTTP method
|
|
user_id: User ID if available
|
|
"""
|
|
context = {
|
|
"request_id": request_id,
|
|
"request_path": request_path,
|
|
"request_method": request_method,
|
|
"user_id": user_id,
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
self.context_stack.append(context)
|
|
|
|
def pop_context(self) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Pop request context from stack.
|
|
|
|
Returns:
|
|
Context dictionary or None if empty
|
|
"""
|
|
return self.context_stack.pop() if self.context_stack else None
|
|
|
|
def get_current_context(self) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Get current request context.
|
|
|
|
Returns:
|
|
Current context or None if empty
|
|
"""
|
|
return self.context_stack[-1] if self.context_stack else None
|
|
|
|
|
|
# Global request context manager
|
|
_context_manager: Optional[RequestContextManager] = None
|
|
|
|
|
|
def get_context_manager() -> RequestContextManager:
|
|
"""
|
|
Get or create global context manager instance.
|
|
|
|
Returns:
|
|
RequestContextManager instance
|
|
"""
|
|
global _context_manager
|
|
if _context_manager is None:
|
|
_context_manager = RequestContextManager()
|
|
return _context_manager
|