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