Files
thrilltrack-explorer/src/lib/requestTracking.ts
2025-11-03 18:30:07 +00:00

167 lines
5.0 KiB
TypeScript

/**
* 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<T>(
options: RequestTrackingOptions,
fn: (context: RequestContext) => Promise<T>
): 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<void> {
// 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();
}