| """ |
| 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") |
|
|