| | """
|
| | Production-Grade Structured Logging
|
| | """
|
| |
|
| | import logging
|
| | import sys
|
| | import json
|
| | from datetime import datetime
|
| | from typing import Any, Dict
|
| | from pathlib import Path
|
| |
|
| | from pythonjsonlogger import jsonlogger
|
| |
|
| | from .config import settings
|
| |
|
| |
|
| | class CustomJsonFormatter(jsonlogger.JsonFormatter):
|
| | """Custom JSON formatter with additional fields"""
|
| |
|
| | def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, message_dict: dict):
|
| | super().add_fields(log_record, record, message_dict)
|
| |
|
| |
|
| | log_record['timestamp'] = datetime.utcnow().isoformat()
|
| |
|
| |
|
| | log_record['level'] = record.levelname
|
| |
|
| |
|
| | log_record['app'] = settings.APP_NAME
|
| | log_record['version'] = settings.APP_VERSION
|
| | log_record['environment'] = settings.ENVIRONMENT
|
| |
|
| |
|
| | if hasattr(record, 'request_id'):
|
| | log_record['request_id'] = record.request_id
|
| |
|
| |
|
| | if hasattr(record, 'user_id'):
|
| | log_record['user_id'] = record.user_id
|
| |
|
| |
|
| | def setup_logging():
|
| | """Configure application logging"""
|
| |
|
| |
|
| | logger = logging.getLogger()
|
| | logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper()))
|
| |
|
| |
|
| | logger.handlers = []
|
| |
|
| |
|
| | console_handler = logging.StreamHandler(sys.stdout)
|
| |
|
| | if settings.LOG_FORMAT == "json":
|
| | console_formatter = CustomJsonFormatter(
|
| | '%(timestamp)s %(level)s %(name)s %(message)s'
|
| | )
|
| | else:
|
| | console_formatter = logging.Formatter(
|
| | '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| | )
|
| |
|
| | console_handler.setFormatter(console_formatter)
|
| | logger.addHandler(console_handler)
|
| |
|
| |
|
| | if settings.LOG_FILE:
|
| | log_file = Path(settings.LOG_FILE)
|
| | log_file.parent.mkdir(parents=True, exist_ok=True)
|
| |
|
| | file_handler = logging.FileHandler(log_file)
|
| |
|
| | if settings.LOG_FORMAT == "json":
|
| | file_formatter = CustomJsonFormatter(
|
| | '%(timestamp)s %(level)s %(name)s %(message)s'
|
| | )
|
| | else:
|
| | file_formatter = logging.Formatter(
|
| | '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| | )
|
| |
|
| | file_handler.setFormatter(file_formatter)
|
| | logger.addHandler(file_handler)
|
| |
|
| | return logger
|
| |
|
| |
|
| |
|
| | logger = setup_logging()
|
| |
|
| |
|
| | def log_api_request(
|
| | method: str,
|
| | path: str,
|
| | status_code: int,
|
| | duration_ms: float,
|
| | user_id: str = None,
|
| | request_id: str = None
|
| | ):
|
| | """Log API request with structured data"""
|
| | logger.info(
|
| | "API Request",
|
| | extra={
|
| | "method": method,
|
| | "path": path,
|
| | "status_code": status_code,
|
| | "duration_ms": duration_ms,
|
| | "user_id": user_id,
|
| | "request_id": request_id,
|
| | "event_type": "api_request"
|
| | }
|
| | )
|
| |
|
| |
|
| | def log_prediction(
|
| | model_type: str,
|
| | input_size: int,
|
| | confidence: float,
|
| | duration_ms: float,
|
| | cached: bool = False,
|
| | user_id: str = None
|
| | ):
|
| | """Log ML prediction with metrics"""
|
| | logger.info(
|
| | "ML Prediction",
|
| | extra={
|
| | "model_type": model_type,
|
| | "input_size": input_size,
|
| | "confidence": confidence,
|
| | "duration_ms": duration_ms,
|
| | "cached": cached,
|
| | "user_id": user_id,
|
| | "event_type": "prediction"
|
| | }
|
| | )
|
| |
|
| |
|
| | def log_error(
|
| | error: Exception,
|
| | context: Dict[str, Any] = None,
|
| | user_id: str = None,
|
| | request_id: str = None
|
| | ):
|
| | """Log error with full context"""
|
| | logger.error(
|
| | f"Error: {str(error)}",
|
| | extra={
|
| | "error_type": type(error).__name__,
|
| | "error_message": str(error),
|
| | "context": context or {},
|
| | "user_id": user_id,
|
| | "request_id": request_id,
|
| | "event_type": "error"
|
| | },
|
| | exc_info=True
|
| | )
|
| |
|
| |
|
| | if __name__ == "__main__":
|
| |
|
| | logger.info("Application starting")
|
| | logger.debug("Debug message")
|
| | logger.warning("Warning message")
|
| | logger.error("Error message")
|
| |
|
| | log_api_request("GET", "/api/v1/health", 200, 5.2, request_id="test-123")
|
| | log_prediction("deepfake", 1024, 0.95, 125.5, cached=False, user_id="user-1")
|
| |
|