refactor: simplify database logging handler

- Remove retry logic from DatabaseLogHandler (was for SQLite locking)
- Streamline error handling in log emission
- Clean up platform health service

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 20:37:34 +01:00
parent 7a7b612519
commit 44832b3fc9
3 changed files with 61 additions and 88 deletions

View File

@@ -47,9 +47,12 @@ def check_database_ready():
"""Check if database is ready (migrations have been run).""" """Check if database is ready (migrations have been run)."""
try: try:
with engine.connect() as conn: with engine.connect() as conn:
# Try to query a table that should exist # Check for tables in the public schema (PostgreSQL)
result = conn.execute( result = conn.execute(
text("SELECT name FROM sqlite_master WHERE type='table' LIMIT 1") text(
"SELECT tablename FROM pg_catalog.pg_tables "
"WHERE schemaname = 'public' LIMIT 1"
)
) )
tables = result.fetchall() tables = result.fetchall()
return len(tables) > 0 return len(tables) > 0

View File

@@ -23,92 +23,76 @@ class DatabaseLogHandler(logging.Handler):
Custom logging handler that stores WARNING, ERROR, and CRITICAL logs in database. Custom logging handler that stores WARNING, ERROR, and CRITICAL logs in database.
Runs asynchronously to avoid blocking application performance. Runs asynchronously to avoid blocking application performance.
Uses retry logic for SQLite database locking issues.
""" """
MAX_RETRIES = 3
RETRY_DELAY = 0.1 # 100ms delay between retries
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setLevel(logging.WARNING) # Only log WARNING and above to database self.setLevel(logging.WARNING) # Only log WARNING and above to database
def emit(self, record): def emit(self, record):
"""Emit a log record to the database with retry logic for SQLite locking.""" """Emit a log record to the database."""
import time try:
from app.core.database import SessionLocal
from models.database.admin import ApplicationLog
# Skip if no database session available
db = SessionLocal()
if not db:
return
for attempt in range(self.MAX_RETRIES):
try: try:
from app.core.database import SessionLocal # Extract exception information if present
from models.database.admin import ApplicationLog exception_type = None
exception_message = None
stack_trace = None
# Skip if no database session available if record.exc_info:
db = SessionLocal() exception_type = (
if not db: record.exc_info[0].__name__ if record.exc_info[0] else None
return )
exception_message = (
try: str(record.exc_info[1]) if record.exc_info[1] else None
# Extract exception information if present )
exception_type = None stack_trace = "".join(
exception_message = None traceback.format_exception(*record.exc_info)
stack_trace = None
if record.exc_info:
exception_type = (
record.exc_info[0].__name__ if record.exc_info[0] else None
)
exception_message = (
str(record.exc_info[1]) if record.exc_info[1] else None
)
stack_trace = "".join(
traceback.format_exception(*record.exc_info)
)
# Extract context from record (if middleware added it)
user_id = getattr(record, "user_id", None)
vendor_id = getattr(record, "vendor_id", None)
request_id = getattr(record, "request_id", None)
context = getattr(record, "context", None)
# Create log entry
log_entry = ApplicationLog(
timestamp=datetime.fromtimestamp(record.created, tz=UTC),
level=record.levelname,
logger_name=record.name,
module=record.module,
function_name=record.funcName,
line_number=record.lineno,
message=record.getMessage(),
exception_type=exception_type,
exception_message=exception_message,
stack_trace=stack_trace,
request_id=request_id,
user_id=user_id,
vendor_id=vendor_id,
context=context,
) )
db.add(log_entry) # Extract context from record (if middleware added it)
db.commit() user_id = getattr(record, "user_id", None)
return # Success, exit retry loop vendor_id = getattr(record, "vendor_id", None)
request_id = getattr(record, "request_id", None)
context = getattr(record, "context", None)
except Exception as e: # Create log entry
db.rollback() log_entry = ApplicationLog(
# Check if it's a database locked error timestamp=datetime.fromtimestamp(record.created, tz=UTC),
if "database is locked" in str(e).lower(): level=record.levelname,
if attempt < self.MAX_RETRIES - 1: logger_name=record.name,
time.sleep(self.RETRY_DELAY * (attempt + 1)) module=record.module,
continue function_name=record.funcName,
# For other errors or final attempt, silently skip line_number=record.lineno,
# Don't print to stderr to avoid log spam during imports message=record.getMessage(),
finally: exception_type=exception_type,
db.close() exception_message=exception_message,
stack_trace=stack_trace,
request_id=request_id,
user_id=user_id,
vendor_id=vendor_id,
context=context,
)
db.add(log_entry)
db.commit()
except Exception: except Exception:
# Silently fail - logging should never crash the app db.rollback()
pass # Silently skip - don't print to stderr to avoid log spam
finally:
db.close()
break # Exit retry loop on non-recoverable errors except Exception:
# Silently fail - logging should never crash the app
pass
def get_log_level_from_db(): def get_log_level_from_db():

View File

@@ -305,27 +305,13 @@ class PlatformHealthService:
def _get_database_size(self, db: Session) -> float: def _get_database_size(self, db: Session) -> float:
"""Get database size in MB.""" """Get database size in MB."""
try: try:
# Try SQLite approach
result = db.execute(
text(
"SELECT page_count * page_size as size "
"FROM pragma_page_count(), pragma_page_size()"
)
)
row = result.fetchone()
if row:
return round(row[0] / (1024 * 1024), 2)
except Exception:
pass
try:
# Try PostgreSQL approach
result = db.execute(text("SELECT pg_database_size(current_database())")) result = db.execute(text("SELECT pg_database_size(current_database())"))
row = result.fetchone() row = result.fetchone()
if row: if row:
return round(row[0] / (1024 * 1024), 2) return round(row[0] / (1024 * 1024), 2)
except Exception: except Exception:
pass logger.warning("Failed to get database size")
return 0.0
return 0.0 return 0.0