mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 15:52:52 +03:00
171 lines
6.0 KiB
TypeScript
171 lines
6.0 KiB
TypeScript
/**
|
|
* Detailed Request Logs DB Layer (#378)
|
|
*
|
|
* Legacy compatibility layer for detailed request logs.
|
|
* New requests now store pipeline details inside unified call log artifacts.
|
|
* This module remains available for reading historical request_detail_logs rows.
|
|
*/
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { getDbInstance } from "./core";
|
|
import { getSettings } from "./settings";
|
|
import { isNoLog } from "../compliance";
|
|
import {
|
|
protectPayloadForLog,
|
|
serializePayloadForStorage,
|
|
parseStoredPayload,
|
|
} from "../logPayloads";
|
|
import { compactStructuredStreamPayload } from "@omniroute/open-sse/utils/streamPayloadCollector.ts";
|
|
|
|
export interface RequestDetailLog {
|
|
id?: string;
|
|
call_log_id?: string | null;
|
|
timestamp?: string;
|
|
client_request?: unknown | null;
|
|
translated_request?: unknown | null;
|
|
provider_response?: unknown | null;
|
|
client_response?: unknown | null;
|
|
provider?: string | null;
|
|
model?: string | null;
|
|
source_format?: string | null;
|
|
target_format?: string | null;
|
|
duration_ms?: number;
|
|
api_key_id?: string | null;
|
|
no_log?: boolean;
|
|
}
|
|
|
|
let requestDetailLogsTableExistsCache: boolean | undefined;
|
|
|
|
function requestDetailLogsTableExists(): boolean {
|
|
if (requestDetailLogsTableExistsCache !== undefined) {
|
|
return requestDetailLogsTableExistsCache;
|
|
}
|
|
|
|
const db = getDbInstance();
|
|
const row = db
|
|
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'request_detail_logs'")
|
|
.get() as { name?: string } | undefined;
|
|
requestDetailLogsTableExistsCache = Boolean(row?.name);
|
|
return requestDetailLogsTableExistsCache;
|
|
}
|
|
|
|
export function resetRequestDetailLogsTableExistsCache(): void {
|
|
requestDetailLogsTableExistsCache = undefined;
|
|
}
|
|
|
|
/** Returns true if detailed logging is enabled in settings */
|
|
export async function isDetailedLoggingEnabled(): Promise<boolean> {
|
|
try {
|
|
const settings = await getSettings();
|
|
const val = settings.call_log_pipeline_enabled;
|
|
return val === true || val === "1" || val === "true";
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Save a detailed log entry — caller must verify isDetailedLoggingEnabled() first */
|
|
export function saveRequestDetailLog(entry: RequestDetailLog): void {
|
|
const noLogEnabled =
|
|
Boolean(entry.no_log) || (entry.api_key_id ? isNoLog(entry.api_key_id) : false);
|
|
if (noLogEnabled || !requestDetailLogsTableExists()) return;
|
|
|
|
const db = getDbInstance();
|
|
const id = entry.id ?? uuidv4();
|
|
const timestamp = entry.timestamp ?? new Date().toISOString();
|
|
const compactProviderResponse = compactStructuredStreamPayload(entry.provider_response);
|
|
const compactClientResponse = compactStructuredStreamPayload(entry.client_response);
|
|
|
|
db.prepare(
|
|
`
|
|
INSERT INTO request_detail_logs
|
|
(id, call_log_id, timestamp, client_request, translated_request,
|
|
provider_response, client_response, provider, model, source_format, target_format, duration_ms)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`
|
|
).run(
|
|
id,
|
|
entry.call_log_id ?? null,
|
|
timestamp,
|
|
serializePayloadForStorage(protectPayloadForLog(entry.client_request)),
|
|
serializePayloadForStorage(protectPayloadForLog(entry.translated_request)),
|
|
serializePayloadForStorage(protectPayloadForLog(compactProviderResponse)),
|
|
serializePayloadForStorage(protectPayloadForLog(compactClientResponse)),
|
|
entry.provider ?? null,
|
|
entry.model ?? null,
|
|
entry.source_format ?? null,
|
|
entry.target_format ?? null,
|
|
entry.duration_ms ?? 0
|
|
);
|
|
}
|
|
|
|
/** Fetch detailed logs (latest first) */
|
|
export function getRequestDetailLogs(limit = 50, offset = 0): RequestDetailLog[] {
|
|
if (!requestDetailLogsTableExists()) return [];
|
|
const db = getDbInstance();
|
|
const rows = db
|
|
.prepare(
|
|
`
|
|
SELECT * FROM request_detail_logs
|
|
ORDER BY timestamp DESC
|
|
LIMIT ? OFFSET ?
|
|
`
|
|
)
|
|
.all(limit, offset) as Array<Record<string, unknown>>;
|
|
|
|
return rows.map(mapDetailedLogRow);
|
|
}
|
|
|
|
/** Get a single detailed log by ID */
|
|
export function getRequestDetailLogById(id: string): RequestDetailLog | null {
|
|
if (!requestDetailLogsTableExists()) return null;
|
|
const db = getDbInstance();
|
|
const row = db.prepare("SELECT * FROM request_detail_logs WHERE id = ?").get(id) as
|
|
| Record<string, unknown>
|
|
| undefined;
|
|
return row ? mapDetailedLogRow(row) : null;
|
|
}
|
|
|
|
/** Get the most recent detailed log for a call log ID */
|
|
export function getRequestDetailLogByCallLogId(callLogId: string): RequestDetailLog | null {
|
|
if (!requestDetailLogsTableExists()) return null;
|
|
const db = getDbInstance();
|
|
const row = db
|
|
.prepare(
|
|
`
|
|
SELECT * FROM request_detail_logs
|
|
WHERE call_log_id = ?
|
|
ORDER BY timestamp DESC
|
|
LIMIT 1
|
|
`
|
|
)
|
|
.get(callLogId) as Record<string, unknown> | undefined;
|
|
return row ? mapDetailedLogRow(row) : null;
|
|
}
|
|
|
|
/** Get total count of detailed logs */
|
|
export function getRequestDetailLogCount(): number {
|
|
if (!requestDetailLogsTableExists()) return 0;
|
|
const db = getDbInstance();
|
|
const row = db.prepare("SELECT COUNT(*) as cnt FROM request_detail_logs").get() as {
|
|
cnt: number;
|
|
};
|
|
return row?.cnt ?? 0;
|
|
}
|
|
|
|
function mapDetailedLogRow(row: Record<string, unknown>): RequestDetailLog {
|
|
return {
|
|
id: typeof row.id === "string" ? row.id : undefined,
|
|
call_log_id: typeof row.call_log_id === "string" ? row.call_log_id : null,
|
|
timestamp: typeof row.timestamp === "string" ? row.timestamp : undefined,
|
|
client_request: parseStoredPayload(row.client_request),
|
|
translated_request: parseStoredPayload(row.translated_request),
|
|
provider_response: parseStoredPayload(row.provider_response),
|
|
client_response: parseStoredPayload(row.client_response),
|
|
provider: typeof row.provider === "string" ? row.provider : null,
|
|
model: typeof row.model === "string" ? row.model : null,
|
|
source_format: typeof row.source_format === "string" ? row.source_format : null,
|
|
target_format: typeof row.target_format === "string" ? row.target_format : null,
|
|
duration_ms: typeof row.duration_ms === "number" ? row.duration_ms : 0,
|
|
};
|
|
}
|