refactor: consolidate logging to shared aitbc-core package and upgrade database dependencies

- Upgrade SQLAlchemy to 2.0.47 with asyncio extras in blockchain-node and coordinator-api
- Add asyncpg >=0.29.0 for PostgreSQL async support
- Remove uvloop as optional dependency, make it required >=0.22.0
- Delete duplicate logger.py from blockchain-node (117 lines)
- Refactor coordinator-api logging to use shared aitbc.logging from aitbc-core package
- Add aitbc-core package dependency to coordinator
This commit is contained in:
oib
2026-02-28 21:09:14 +01:00
parent d072acb468
commit f6ee77f497
23 changed files with 6179 additions and 553 deletions

View File

@@ -1,101 +1,3 @@
"""
Logging configuration for the AITBC Coordinator API
from aitbc.logging import get_logger, setup_logger
Provides structured JSON logging for better observability and log parsing.
"""
import logging
import sys
import json
from datetime import datetime
from typing import Any, Dict, Optional
from pythonjsonlogger import jsonlogger
class StructuredLogFormatter(jsonlogger.JsonFormatter):
"""Custom JSON formatter for structured logging."""
def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, message_dict: Dict[str, Any]) -> None:
super().add_fields(log_record, record, message_dict)
log_record['timestamp'] = datetime.utcnow().isoformat() + 'Z'
log_record['service'] = 'aitbc-coordinator-api'
log_record['level'] = record.levelname
log_record['logger'] = record.name
log_record['module'] = record.module
log_record['function'] = record.funcName
log_record['line'] = record.lineno
if record.exc_info:
log_record['exception'] = self.format_exception(record.exc_info)
@staticmethod
def format_exception(exc_info) -> Optional[Dict[str, Any]]:
"""Format exception info for JSON output."""
if exc_info is None:
return None
import traceback
return {
'type': exc_info[0].__name__ if exc_info[0] else None,
'message': str(exc_info[1]) if exc_info[1] else None,
'traceback': traceback.format_exception(*exc_info) if exc_info[0] else None
}
class AuditLogger:
"""Audit logger for tracking sensitive operations."""
def __init__(self, logger: logging.Logger):
self.logger = logger
def log(self, action: str, user_id: Optional[str] = None, resource_id: Optional[str] = None,
details: Optional[Dict[str, Any]] = None, success: bool = True) -> None:
"""Log an audit event."""
self.logger.info(
"audit_event",
extra={
'audit': {
'action': action,
'user_id': user_id,
'resource_id': resource_id,
'details': details or {},
'success': success
}
}
)
def setup_logging(level: str = "INFO", json_format: bool = True) -> None:
"""Setup structured logging for the application."""
root_logger = logging.getLogger()
root_logger.handlers.clear()
if json_format:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(StructuredLogFormatter(
'%(timestamp)s %(level)s %(message)s'
))
else:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
)
root_logger.addHandler(handler)
root_logger.setLevel(getattr(logging, level.upper()))
logging.getLogger('uvicorn').setLevel(level)
logging.getLogger('uvicorn.access').setLevel(level)
def get_logger(name: str) -> logging.Logger:
"""Get a logger instance."""
return logging.getLogger(name)
def get_audit_logger(name: str = "audit") -> AuditLogger:
"""Get an audit logger instance."""
return AuditLogger(get_logger(name))
# Initialize default logging on import
setup_logging()
__all__ = ["get_logger", "setup_logger"]