Spaces:
Sleeping
Sleeping
Veerandi Moulik
fix: improve database logging β dual-write, background threads, response timing
59143a9 | """ | |
| Database layer β Supabase client + SQLite fallback + conversation logging. | |
| Logging strategy: | |
| β’ If Supabase is available β write there too (cloud backup) | |
| β’ Always write to SQLite (admin reads from here) | |
| β’ Writes happen in a background thread so the user never waits | |
| Admin endpoints always read SQLite so they work even when Supabase is down. | |
| """ | |
| import sqlite3 | |
| import logging | |
| import threading | |
| from contextlib import contextmanager | |
| from .config import SUPABASE_URL, SUPABASE_KEY, DB_PATH | |
| logger = logging.getLogger(__name__) | |
| # ββ Supabase client βββββββββββββββββββββββββββββββββββββββ | |
| supabase = None | |
| if SUPABASE_URL and SUPABASE_KEY: | |
| try: | |
| from supabase import create_client | |
| supabase = create_client(SUPABASE_URL, SUPABASE_KEY) | |
| logger.info("Supabase client initialized successfully") | |
| except Exception as e: | |
| logger.error(f"Failed to initialize Supabase: {e}") | |
| else: | |
| logger.warning("Supabase credentials not found β database features disabled") | |
| # ββ SQLite helpers ββββββββββββββββββββββββββββββββββββββββ | |
| def get_db(): | |
| """Context-managed SQLite connection with WAL mode + row factory.""" | |
| conn = sqlite3.connect(DB_PATH, check_same_thread=False) | |
| conn.row_factory = sqlite3.Row | |
| conn.execute("PRAGMA journal_mode=WAL") | |
| try: | |
| yield conn | |
| finally: | |
| conn.close() | |
| def init_database(): | |
| """Create the conversations table + indexes if they don't exist.""" | |
| with get_db() as conn: | |
| cursor = conn.cursor() | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS conversations ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| session_id TEXT, | |
| timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| user_message TEXT NOT NULL, | |
| bot_response TEXT NOT NULL, | |
| intent TEXT, | |
| model_used TEXT, | |
| response_type TEXT, | |
| ip_address TEXT, | |
| response_time_ms INTEGER | |
| ) | |
| ''') | |
| cursor.execute('CREATE INDEX IF NOT EXISTS idx_timestamp ON conversations(timestamp)') | |
| cursor.execute('CREATE INDEX IF NOT EXISTS idx_session ON conversations(session_id)') | |
| cursor.execute('CREATE INDEX IF NOT EXISTS idx_intent ON conversations(intent)') | |
| conn.commit() | |
| # Migrate: add response_time_ms column if missing (existing DBs) | |
| try: | |
| with get_db() as conn: | |
| conn.execute("SELECT response_time_ms FROM conversations LIMIT 1") | |
| except sqlite3.OperationalError: | |
| with get_db() as conn: | |
| conn.execute("ALTER TABLE conversations ADD COLUMN response_time_ms INTEGER") | |
| conn.commit() | |
| logger.info("Migrated: added response_time_ms column") | |
| logger.info("SQLite database initialized") | |
| # ββ Background logging ββββββββββββββββββββββββββββββββββββ | |
| def _log_in_background(session_id, user_message, bot_response, | |
| intent, model_used, response_type, ip_address, | |
| response_time_ms): | |
| """Write to both SQLite (always) and Supabase (if available) in a daemon thread.""" | |
| def _write(): | |
| # 1. Always write to SQLite (admin reads from here) | |
| try: | |
| with get_db() as conn: | |
| conn.execute(''' | |
| INSERT INTO conversations | |
| (session_id, user_message, bot_response, intent, | |
| model_used, response_type, ip_address, response_time_ms) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?) | |
| ''', (session_id, user_message, bot_response, intent, | |
| model_used, response_type, ip_address, response_time_ms)) | |
| conn.commit() | |
| except Exception as e: | |
| logger.error(f"Failed to log conversation to SQLite: {e}") | |
| # 2. Also write to Supabase if available | |
| if supabase: | |
| try: | |
| supabase.table('conversations').insert({ | |
| "session_id": session_id, | |
| "user_message": user_message, | |
| "bot_response": bot_response, | |
| "intent": intent, | |
| "model_used": model_used, | |
| "response_type": response_type, | |
| "ip_address": ip_address, | |
| "response_time_ms": response_time_ms, | |
| }).execute() | |
| except Exception as e: | |
| logger.error(f"Failed to log conversation to Supabase: {e}") | |
| thread = threading.Thread(target=_write, daemon=True) | |
| thread.start() | |
| def log_conversation(session_id, user_message, bot_response, | |
| intent=None, model_used=None, response_type=None, | |
| ip_address=None, response_time_ms=None): | |
| """ | |
| Log a conversation (non-blocking). | |
| Writes to SQLite always + Supabase if configured, in a background thread. | |
| """ | |
| _log_in_background( | |
| session_id, user_message, bot_response, | |
| intent, model_used, response_type, ip_address, response_time_ms, | |
| ) | |
| def purge_old_logs(days=90): | |
| """Delete conversation logs older than N days. Returns count deleted.""" | |
| with get_db() as conn: | |
| cursor = conn.execute( | |
| "DELETE FROM conversations WHERE timestamp < datetime('now', ?)", | |
| (f'-{days} days',), | |
| ) | |
| conn.commit() | |
| return cursor.rowcount | |
| # Initialize on import | |
| init_database() | |