eldouma-graphics / server /usageLog.mjs
Moeeldouma's picture
Deploy Eldouma Graphics — Docker Space, bring-your-own-key demo
39d98a0 verified
Raw
History Blame Contribute Delete
2.99 kB
// AI request + cost logging. A middleware records every AI/render request (user, route, duration, status,
// rough est. cost) to Postgres (ai_request_log) or a JSONL file fallback, tracks a rolling daily spend
// estimate, and warns when it crosses DAILY_SPEND_ALERT_USD. No prompts/PII are stored here.
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { pgEnabled, pgQuery } from "./db.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const LOG_FILE = path.join(__dirname, "..", ".cache", "ai-usage.jsonl");
try { fs.mkdirSync(path.dirname(LOG_FILE), { recursive: true }); } catch { /* ignore */ }
// coarse $ per call by route (tune from real provider bills). Only billed for requests that actually ran.
const COST_PER_CALL = {
chat: 0.0015, transcribe: 0.01, script: 0.0015, "generate-scenes": 0.004,
"fill-scene": 0.0015, translate: 0.003, analyze: 0.002, export: 0.05, "image-to-scene": 0.004,
};
const ROUTE_RE = /^\/api\/(chat\/stream|chat|transcribe-src|transcribe|script|generate-scenes|fill-scene|translate|analyze|export|image-to-scene)\b/;
const routeKey = (p) => {
if (/^\/api\/projects\/[^/]+\/import\b/.test(p)) return "transcribe"; // base-audio import runs STT
const m = p.match(ROUTE_RE);
return m ? m[1].replace("/stream", "").replace("-src", "") : null;
};
let day = "", daySpend = 0, alerted = false;
const ALERT = Number(process.env.DAILY_SPEND_ALERT_USD) || 0;
function trackSpend(cost) {
const today = new Date().toISOString().slice(0, 10);
if (today !== day) { day = today; daySpend = 0; alerted = false; }
daySpend += cost;
if (ALERT > 0 && daySpend >= ALERT && !alerted) {
alerted = true;
console.warn(`[cost] ⚠ daily AI spend estimate crossed $${ALERT} — now ~$${daySpend.toFixed(2)}`);
}
}
async function persist(rec) {
if (pgEnabled()) {
try {
await pgQuery(
`INSERT INTO ai_request_log (user_id, route, model, in_tokens, out_tokens, est_cost, duration_ms, ok)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`,
[rec.userId, rec.route, rec.model ?? null, rec.inTokens ?? null, rec.outTokens ?? null, rec.cost, rec.durationMs, rec.ok],
);
} catch { /* logging must never break a request */ }
} else {
try { fs.appendFileSync(LOG_FILE, JSON.stringify(rec) + "\n"); } catch { /* ignore */ }
}
}
export function aiUsageLogger() {
return (req, res, next) => {
const route = routeKey(req.path);
if (!route) return next();
const t0 = Date.now();
res.on("finish", () => {
const ok = res.statusCode < 400;
const cost = ok ? (COST_PER_CALL[route] ?? 0.002) : 0; // don't bill rejected (402/429/4xx) requests
if (cost) trackSpend(cost);
persist({ userId: req.userId || null, route, durationMs: Date.now() - t0, ok, cost, status: res.statusCode, at: new Date().toISOString() });
});
next();
};
}
export const dailySpend = () => ({ day, usd: +daySpend.toFixed(4) });