/** * 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'; import { breadcrumbManager } from './errorBreadcrumbs'; import { captureEnvironmentContext } from './environmentContext'; import { handleNonCriticalError } from './errorHandler'; import { logger } from './logger'; 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 => { handleNonCriticalError(err, { action: 'Log request metadata (success)', userId: options.userId, metadata: { endpoint: options.endpoint, method: options.method, statusCode: 200, requestId: context.requestId } }); }); // 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, stack: error.stack ? error.stack.slice(0, 5000) : undefined // Limit to 5000 chars } : { type: 'UnknownError', message: String(error), stack: undefined }; // Capture environment context and breadcrumbs const envContext = captureEnvironmentContext(); const breadcrumbs = breadcrumbManager.getAll(); // 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, errorStack: errorInfo.stack, breadcrumbs, userAgent: context.userAgent, clientVersion: context.clientVersion, parentRequestId: options.parentRequestId, traceId: context.traceId, timezone: envContext.timezone, referrer: typeof document !== 'undefined' ? document.referrer : undefined, }).catch(err => { handleNonCriticalError(err, { action: 'Log request metadata (error)', userId: options.userId, metadata: { endpoint: options.endpoint, method: options.method, statusCode: 500, requestId: context.requestId, errorType: errorInfo.type } }); }); // 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; errorStack?: string; breadcrumbs?: any[]; userAgent?: string; clientVersion?: string; parentRequestId?: string; traceId?: string; timezone?: string; referrer?: 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_error_stack: metadata.errorStack ?? undefined, p_breadcrumbs: metadata.breadcrumbs ? JSON.stringify(metadata.breadcrumbs) : '[]', p_environment_context: '{}', // Legacy parameter - no longer used 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, p_timezone: metadata.timezone ?? undefined, p_referrer: metadata.referrer ?? undefined, }); if (error) { // Already logged by handleNonCriticalError in requestTracking } } /** * 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(); }