| """
|
| Structured JSON Logging Configuration
|
| Provides consistent logging across the application
|
| """
|
|
|
| import logging
|
| import json
|
| import sys
|
| from datetime import datetime
|
| from typing import Any, Dict, Optional
|
|
|
|
|
| class JSONFormatter(logging.Formatter):
|
| """Custom JSON formatter for structured logging"""
|
|
|
| def format(self, record: logging.LogRecord) -> str:
|
| """Format log record as JSON"""
|
| log_data = {
|
| "timestamp": datetime.utcnow().isoformat() + "Z",
|
| "level": record.levelname,
|
| "logger": record.name,
|
| "message": record.getMessage(),
|
| }
|
|
|
|
|
| if hasattr(record, 'provider'):
|
| log_data['provider'] = record.provider
|
| if hasattr(record, 'endpoint'):
|
| log_data['endpoint'] = record.endpoint
|
| if hasattr(record, 'duration'):
|
| log_data['duration_ms'] = record.duration
|
| if hasattr(record, 'status'):
|
| log_data['status'] = record.status
|
| if hasattr(record, 'http_code'):
|
| log_data['http_code'] = record.http_code
|
|
|
|
|
| if record.exc_info:
|
| log_data['exception'] = self.formatException(record.exc_info)
|
|
|
|
|
| if record.stack_info:
|
| log_data['stack_trace'] = self.formatStack(record.stack_info)
|
|
|
| return json.dumps(log_data)
|
|
|
|
|
| def setup_logger(name: str, level: str = "INFO") -> logging.Logger:
|
| """
|
| Setup a logger with JSON formatting
|
|
|
| Args:
|
| name: Logger name
|
| level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
|
| Returns:
|
| Configured logger instance
|
| """
|
| logger = logging.getLogger(name)
|
|
|
|
|
| logger.handlers = []
|
|
|
|
|
| logger.setLevel(getattr(logging, level.upper()))
|
|
|
|
|
| console_handler = logging.StreamHandler(sys.stdout)
|
| console_handler.setLevel(getattr(logging, level.upper()))
|
|
|
|
|
| json_formatter = JSONFormatter()
|
| console_handler.setFormatter(json_formatter)
|
|
|
|
|
| logger.addHandler(console_handler)
|
|
|
|
|
| logger.propagate = False
|
|
|
| return logger
|
|
|
|
|
| def log_api_request(
|
| logger: logging.Logger,
|
| provider: str,
|
| endpoint: str,
|
| duration_ms: float,
|
| status: str,
|
| http_code: Optional[int] = None,
|
| level: str = "INFO"
|
| ):
|
| """
|
| Log an API request with structured data
|
|
|
| Args:
|
| logger: Logger instance
|
| provider: Provider name
|
| endpoint: API endpoint
|
| duration_ms: Request duration in milliseconds
|
| status: Request status (success/error)
|
| http_code: HTTP status code
|
| level: Log level
|
| """
|
| log_level = getattr(logging, level.upper())
|
|
|
| extra = {
|
| 'provider': provider,
|
| 'endpoint': endpoint,
|
| 'duration': duration_ms,
|
| 'status': status,
|
| }
|
|
|
| if http_code:
|
| extra['http_code'] = http_code
|
|
|
| message = f"{provider} - {endpoint} - {status} - {duration_ms}ms"
|
|
|
| logger.log(log_level, message, extra=extra)
|
|
|
|
|
| def log_error(
|
| logger: logging.Logger,
|
| provider: str,
|
| error_type: str,
|
| error_message: str,
|
| endpoint: Optional[str] = None,
|
| exc_info: bool = False
|
| ):
|
| """
|
| Log an error with structured data
|
|
|
| Args:
|
| logger: Logger instance
|
| provider: Provider name
|
| error_type: Type of error
|
| error_message: Error message
|
| endpoint: API endpoint (optional)
|
| exc_info: Include exception info
|
| """
|
| extra = {
|
| 'provider': provider,
|
| 'error_type': error_type,
|
| }
|
|
|
| if endpoint:
|
| extra['endpoint'] = endpoint
|
|
|
| message = f"{provider} - {error_type}: {error_message}"
|
|
|
| logger.error(message, extra=extra, exc_info=exc_info)
|
|
|
|
|
|
|
| app_logger = setup_logger("crypto_monitor", level="INFO")
|
|
|