/** * Request Tracking Service * Tracks API requests with correlation IDs and stores metadata for monitoring */ import { supabase } from '@/integrations/supabase/client'; import { requestContext, type RequestContext } from './requestContext'; export interface RequestTrackingOptions { endpoint: string; method: string; userId?: string; parentRequestId?: string; traceId?: string; } export interface RequestResult { requestId: string; statusCode: number; duration: number; error?: { type: string; message: string; }; } /** * Track a request and store metadata * Returns requestId for correlation and support */ export async function trackRequest( options: RequestTrackingOptions, fn: (context: RequestContext) => Promise ): Promise<{ result: T; requestId: string; duration: number }> { const context = requestContext.create(options.userId, options.traceId); const start = Date.now(); try { const result = await fn(context); const duration = Date.now() - start; // Log to database (fire and forget - don't block response) logRequestMetadata({ requestId: context.requestId, userId: options.userId, endpoint: options.endpoint, method: options.method, statusCode: 200, duration, userAgent: context.userAgent, clientVersion: context.clientVersion, parentRequestId: options.parentRequestId, traceId: context.traceId, }).catch(err => { console.error('[RequestTracking] Failed to log metadata:', err); }); // Cleanup context requestContext.cleanup(context.requestId); return { result, requestId: context.requestId, duration }; } catch (error: unknown) { const duration = Date.now() - start; const errorInfo = error instanceof Error ? { type: error.name, message: error.message } : { type: 'UnknownError', message: String(error) }; // Log error to database (fire and forget) logRequestMetadata({ requestId: context.requestId, userId: options.userId, endpoint: options.endpoint, method: options.method, statusCode: 500, duration, errorType: errorInfo.type, errorMessage: errorInfo.message, userAgent: context.userAgent, clientVersion: context.clientVersion, parentRequestId: options.parentRequestId, traceId: context.traceId, }).catch(err => { console.error('[RequestTracking] Failed to log error metadata:', err); }); // Cleanup context requestContext.cleanup(context.requestId); throw error; } } interface RequestMetadata { requestId: string; userId?: string; endpoint: string; method: string; statusCode: number; duration: number; errorType?: string; errorMessage?: string; userAgent?: string; clientVersion?: string; parentRequestId?: string; traceId?: string; } async function logRequestMetadata(metadata: RequestMetadata): Promise { // Safe cast - RPC function exists in database const { error } = await supabase.rpc('log_request_metadata' as 'log_request_metadata', { p_request_id: metadata.requestId, p_user_id: metadata.userId ?? undefined, p_endpoint: metadata.endpoint, p_method: metadata.method, p_status_code: metadata.statusCode, p_duration_ms: metadata.duration, p_error_type: metadata.errorType ?? undefined, p_error_message: metadata.errorMessage ?? undefined, p_user_agent: metadata.userAgent ?? undefined, p_client_version: metadata.clientVersion ?? undefined, p_parent_request_id: metadata.parentRequestId ?? undefined, p_trace_id: metadata.traceId ?? undefined, }); if (error) { console.error('[RequestTracking] Failed to log metadata:', error); } } /** * Simple wrapper for tracking without async operations */ export function createRequestContext( userId?: string, traceId?: string ): RequestContext { return requestContext.create(userId, traceId); } /** * Get request context for current operation */ export function getCurrentRequestContext(): RequestContext | undefined { return requestContext.getCurrentContext(); }