/** * 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 { 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 => { logger.error('Failed to log request metadata', { error: err, context: 'RequestTracking' }); }); // 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 breadcrumbs and environment const breadcrumbs = breadcrumbManager.getAll(); const environment = captureEnvironmentContext(); // 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, environmentContext: environment, userAgent: context.userAgent, clientVersion: context.clientVersion, parentRequestId: options.parentRequestId, traceId: context.traceId, }).catch(err => { logger.error('Failed to log error metadata', { error: err, context: 'RequestTracking' }); }); // 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[]; environmentContext?: any; 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_error_stack: metadata.errorStack ?? undefined, p_breadcrumbs: metadata.breadcrumbs ? JSON.stringify(metadata.breadcrumbs) : '[]', p_environment_context: metadata.environmentContext ? JSON.stringify(metadata.environmentContext) : '{}', 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) { logger.error('Failed to log metadata to database', { error, context: '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(); }