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:
@@ -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
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user