Skip to main content
Glama
tracer.ts5.21 kB
/** * Request Tracer - Complete lifecycle tracking */ import { Pool } from 'pg'; import { randomUUID } from 'crypto'; import type { ITracer, RequestType, RequestTrace, RoutingDecision, LLMCallTrace, ToolCallTrace, ErrorInfo, } from '../types/tracing.js'; export class Tracer implements ITracer { private activeTraces = new Map<string, RequestTrace>(); constructor(private db: Pool) { } startTrace( requestType: RequestType, conversationId: string, payload: Record<string, unknown>, ): string { const traceId = randomUUID(); const trace: RequestTrace = { id: traceId, conversationId, requestType, requestPayload: payload, routingDecisions: [], llmCalls: [], toolCalls: [], totalCost: 0, totalDurationMs: 0, createdAt: new Date(), }; this.activeTraces.set(traceId, trace); return traceId; } recordRoutingDecision( traceId: string, decision: RoutingDecision, ): void { const trace = this.activeTraces.get(traceId); if (!trace) { console.warn(`[Tracer] No active trace for ${traceId}`); return; } trace.routingDecisions.push(decision); } recordLLMCall(traceId: string, call: LLMCallTrace): void { const trace = this.activeTraces.get(traceId); if (!trace) { console.warn(`[Tracer] No active trace for ${traceId}`); return; } trace.llmCalls.push(call); trace.totalCost += call.cost; } recordToolCall(traceId: string, call: ToolCallTrace): void { const trace = this.activeTraces.get(traceId); if (!trace) { console.warn(`[Tracer] No active trace for ${traceId}`); return; } trace.toolCalls.push(call); } recordError(traceId: string, error: ErrorInfo): void { const trace = this.activeTraces.get(traceId); if (!trace) { console.warn(`[Tracer] No active trace for ${traceId}`); return; } trace.errorInfo = error; } async endTrace( traceId: string, totalDurationMs: number, ): Promise<void> { const trace = this.activeTraces.get(traceId); if (!trace) { console.warn(`[Tracer] No active trace for ${traceId}`); return; } trace.totalDurationMs = totalDurationMs; // Persist to database await this.saveTrace(trace); // Clean up this.activeTraces.delete(traceId); } async getTrace(traceId: string): Promise<RequestTrace | null> { const result = await this.db.query( `SELECT * FROM request_traces WHERE id = $1`, [traceId], ); if (result.rows.length === 0) { return null; } const row = result.rows[0]; return { id: row.id, conversationId: row.conversation_id, requestType: row.request_type, requestPayload: row.request_payload, routingDecisions: row.routing_decisions, llmCalls: row.llm_calls, toolCalls: row.tool_calls, totalCost: parseFloat(row.total_cost), totalDurationMs: row.total_duration_ms, errorInfo: row.error_info, createdAt: row.created_at, }; } private async saveTrace(trace: RequestTrace): Promise<void> { try { await this.db.query( `INSERT INTO request_traces ( id, conversation_id, request_type, request_payload, routing_decisions, llm_calls, tool_calls, total_cost, total_duration_ms, error_info, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [ trace.id, trace.conversationId, trace.requestType, JSON.stringify(trace.requestPayload), JSON.stringify(trace.routingDecisions), JSON.stringify(trace.llmCalls), JSON.stringify(trace.toolCalls), trace.totalCost, trace.totalDurationMs, trace.errorInfo ? JSON.stringify(trace.errorInfo) : null, trace.createdAt, ], ); } catch (error) { console.error('[Tracer] Failed to save trace:', error); } } } /** * Singleton instance */ let tracerInstance: Tracer | null = null; export function initTracer(db: Pool): void { tracerInstance = new Tracer(db); } export function getTracer(): Tracer { if (!tracerInstance) { throw new Error('Tracer not initialized. Call initTracer() first.'); } return tracerInstance; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server