import json import os import sqlite3 import time import uuid DEFAULT_DB_PATH = "/var/lib/recipe/metrics.sqlite3" def db_path() -> str: return os.environ.get("RECIPE_METRICS_DB", DEFAULT_DB_PATH) def initialize() -> None: path = db_path() os.makedirs(os.path.dirname(path), exist_ok=True) with sqlite3.connect(path) as connection: connection.execute("PRAGMA journal_mode=WAL") connection.execute(""" CREATE TABLE IF NOT EXISTS requests ( request_id TEXT PRIMARY KEY, started_at TEXT NOT NULL, client_ip TEXT, user_agent TEXT, method TEXT NOT NULL, path TEXT NOT NULL, image_mime TEXT, image_bytes INTEGER, prompt_chars INTEGER, status_code INTEGER NOT NULL, duration_ms INTEGER NOT NULL, response_bytes INTEGER, usage_json TEXT, error TEXT ) """) connection.execute("CREATE INDEX IF NOT EXISTS idx_requests_started_at ON requests(started_at)") connection.execute("CREATE INDEX IF NOT EXISTS idx_requests_status_code ON requests(status_code)") connection.execute( "CREATE INDEX IF NOT EXISTS idx_requests_client_ip_started_at ON requests(client_ip, started_at)" ) def record(**values) -> None: columns = [ "request_id", "started_at", "client_ip", "user_agent", "method", "path", "image_mime", "image_bytes", "prompt_chars", "status_code", "duration_ms", "response_bytes", "usage_json", "error", ] payload = [values.get(column) for column in columns] with sqlite3.connect(db_path()) as connection: connection.execute( f"INSERT INTO requests ({','.join(columns)}) VALUES ({','.join('?' for _ in columns)})", payload, ) def count_since(client_ip: str, started_at_from: str) -> int: with sqlite3.connect(db_path()) as connection: row = connection.execute( """ SELECT COUNT(*) FROM requests WHERE client_ip = ? AND started_at >= ? AND path IN ('/receipt', '/receipt/', '/recipe', '/recipe/') """, (client_ip, started_at_from), ).fetchone() return int(row[0] if row else 0) def request_context() -> tuple[str, str, float]: return str(uuid.uuid4()), time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), time.monotonic() def usage_json(usage: dict) -> str: return json.dumps(usage, ensure_ascii=True, separators=(",", ":"))