/** * Profound Analytics - Custom log integration * * Buffers HTTP request logs in memory and flushes them in batches to Profound's API. * Runs in Node.js (proxy.ts on ECS), so module-level state persists across requests. * @see https://docs.tryprofound.com/agent-analytics/custom */ import { createLogger } from '@sim/logger' import { env } from '@/lib/core/config/env' import { isHosted } from '@/lib/core/config/env-flags' import { getClientIp } from '@/lib/core/utils/request' import { getBaseDomain } from '@/lib/core/utils/urls' const logger = createLogger('ProfoundAnalytics') const FLUSH_INTERVAL_MS = 10_000 const MAX_BATCH_SIZE = 500 interface ProfoundLogEntry { timestamp: string method: string host: string path: string status_code: number ip: string user_agent: string query_params?: Record referer?: string } let buffer: ProfoundLogEntry[] = [] let flushTimer: NodeJS.Timeout | null = null /** * Returns true if Profound analytics is configured. */ export function isProfoundEnabled(): boolean { return isHosted && Boolean(env.PROFOUND_API_KEY) && Boolean(env.PROFOUND_ENDPOINT) } /** * Flushes buffered log entries to Profound's API. */ async function flush(): Promise { if (buffer.length === 0) return const apiKey = env.PROFOUND_API_KEY if (!apiKey) { buffer = [] return } const endpoint = env.PROFOUND_ENDPOINT if (!endpoint) { buffer = [] return } const entries = buffer.splice(0, MAX_BATCH_SIZE) try { const response = await fetch(endpoint, { method: 'POST', headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify(entries), }) if (!response.ok) { logger.error(`Profound API returned ${response.status}`) } } catch (error) { logger.error('Failed to flush logs to Profound', error) } } function ensureFlushTimer(): void { if (flushTimer) return flushTimer = setInterval(() => { flush().catch(() => {}) }, FLUSH_INTERVAL_MS) flushTimer.unref() } /** * Queues a request log entry for the next batch flush to Profound. */ export function sendToProfound(request: Request, statusCode: number): void { if (!isProfoundEnabled()) return try { const url = new URL(request.url) const queryParams: Record = {} url.searchParams.forEach((value, key) => { queryParams[key] = value }) buffer.push({ timestamp: new Date().toISOString(), method: request.method, host: getBaseDomain(), path: url.pathname, status_code: statusCode, ip: (() => { const resolved = getClientIp(request) return resolved === 'unknown' ? '0.0.0.0' : resolved })(), user_agent: request.headers.get('user-agent') || '', ...(Object.keys(queryParams).length > 0 && { query_params: queryParams }), ...(request.headers.get('referer') && { referer: request.headers.get('referer')! }), }) ensureFlushTimer() if (buffer.length >= MAX_BATCH_SIZE) { flush().catch(() => {}) } } catch (error) { logger.error('Failed to enqueue log entry', error) } }