Skip to main content
Glama
transaction-trace-logger.ts12.9 kB
import { AuditTrailService } from './audit-trail-service.js'; import { AuditEventType, AuditSeverity } from './types.js'; import { createLogger } from '../logger/index.js'; export interface TransactionTrace { transactionId: string; correlationId: string; startTime: number; endTime?: number; status: 'initiated' | 'processing' | 'completed' | 'failed' | 'cancelled'; steps: TransactionStep[]; metadata?: Record<string, any>; } export interface TransactionStep { stepId: string; stepName: string; component: string; startTime: number; endTime?: number; duration?: number; status: 'started' | 'completed' | 'failed' | 'skipped'; input?: any; output?: any; error?: Error; metadata?: Record<string, any>; } export interface TransactionContext { transactionId: string; correlationId: string; agentId?: string; userId?: string; sessionId?: string; source: string; timestamp: number; metadata?: Record<string, any>; } export class TransactionTraceLogger { private auditService: AuditTrailService; private logger = createLogger('transaction-trace'); private activeTraces: Map<string, TransactionTrace> = new Map(); constructor(auditService?: AuditTrailService) { this.auditService = auditService || AuditTrailService.getInstance(); } /** * Start tracing a transaction */ public startTrace( transactionId: string, correlationId?: string, metadata?: Record<string, any> ): string { const traceCorrelationId = correlationId || this.auditService.generateCorrelationId(); const startTime = Date.now(); const trace: TransactionTrace = { transactionId, correlationId: traceCorrelationId, startTime, status: 'initiated', steps: [], metadata }; this.activeTraces.set(transactionId, trace); const message = `Transaction trace started: ${transactionId}`; const eventId = this.auditService.logEvent( AuditEventType.TRANSACTION_INITIATED, message, AuditSeverity.MEDIUM, { correlationId: traceCorrelationId, transactionId, source: 'transaction-trace-logger' }, { startTime, metadata } ); this.logger.info(`Transaction trace started: ${transactionId}`, { eventId, correlationId: traceCorrelationId, transactionId }); return eventId; } /** * Add a step to the transaction trace */ public addStep( transactionId: string, stepName: string, component: string, input?: any, metadata?: Record<string, any> ): string { const trace = this.activeTraces.get(transactionId); if (!trace) { throw new Error(`Transaction trace ${transactionId} not found`); } const stepId = `${component}-${stepName}-${Date.now()}`; const startTime = Date.now(); const step: TransactionStep = { stepId, stepName, component, startTime, status: 'started', input, metadata }; trace.steps.push(step); trace.status = 'processing'; const message = `Transaction step started: ${stepName} (${component})`; const eventId = this.auditService.logEvent( AuditEventType.TRANSACTION_TRACE, message, AuditSeverity.LOW, { correlationId: trace.correlationId, transactionId, source: 'transaction-trace-logger' }, { stepId, stepName, component, startTime, input, metadata } ); this.logger.debug(`Transaction step started: ${stepName}`, { eventId, correlationId: trace.correlationId, transactionId, stepId }); return stepId; } /** * Complete a transaction step */ public completeStep( transactionId: string, stepId: string, output?: any, error?: Error ): void { const trace = this.activeTraces.get(transactionId); if (!trace) { throw new Error(`Transaction trace ${transactionId} not found`); } const step = trace.steps.find(s => s.stepId === stepId); if (!step) { throw new Error(`Step ${stepId} not found in transaction ${transactionId}`); } const endTime = Date.now(); const duration = endTime - step.startTime; step.endTime = endTime; step.duration = duration; step.output = output; step.error = error; step.status = error ? 'failed' : 'completed'; const message = `Transaction step completed: ${step.stepName} (${step.component})`; const severity = error ? AuditSeverity.HIGH : AuditSeverity.LOW; const eventId = this.auditService.logEvent( AuditEventType.TRANSACTION_TRACE, message, severity, { correlationId: trace.correlationId, transactionId, source: 'transaction-trace-logger' }, { stepId, stepName: step.stepName, component: step.component, endTime, duration, output, error: error ? { name: error.name, message: error.message, stack: error.stack } : undefined } ); this.logger.debug(`Transaction step completed: ${step.stepName}`, { eventId, correlationId: trace.correlationId, transactionId, stepId, duration, hasError: !!error }); } /** * Complete a transaction trace */ public completeTrace( transactionId: string, status: 'completed' | 'failed' | 'cancelled', summary?: string, metadata?: Record<string, any> ): string { const trace = this.activeTraces.get(transactionId); if (!trace) { throw new Error(`Transaction trace ${transactionId} not found`); } const endTime = Date.now(); const totalDuration = endTime - trace.startTime; trace.endTime = endTime; trace.status = status; if (metadata) { trace.metadata = { ...trace.metadata, ...metadata }; } this.activeTraces.delete(transactionId); const message = `Transaction trace completed: ${transactionId} - ${status}`; const severity = this.mapTransactionStatusToSeverity(status); const eventId = this.auditService.logEvent( this.mapStatusToEventType(status), message, severity, { correlationId: trace.correlationId, transactionId, source: 'transaction-trace-logger' }, { status, startTime: trace.startTime, endTime, totalDuration, totalSteps: trace.steps.length, summary, metadata: trace.metadata, steps: trace.steps } ); this.logger.info(`Transaction trace completed: ${transactionId} - ${status}`, { eventId, correlationId: trace.correlationId, transactionId, totalDuration, totalSteps: trace.steps.length }); return eventId; } /** * Log transaction sent to blockchain */ public logTransactionSent( transactionId: string, txIdentifier: string, correlationId?: string ): string { const message = `Transaction sent to blockchain: ${transactionId} - ${txIdentifier}`; const eventId = this.auditService.logEvent( AuditEventType.TRANSACTION_SENT, message, AuditSeverity.MEDIUM, { correlationId, transactionId, source: 'transaction-trace-logger' }, { transactionId, txIdentifier, timestamp: Date.now() } ); this.logger.info(`Transaction sent to blockchain: ${transactionId}`, { eventId, correlationId, transactionId, txIdentifier }); return eventId; } /** * Log transaction failure */ public logTransactionFailure( transactionId: string, error: Error, context?: any, correlationId?: string ): string { const message = `Transaction failed: ${transactionId} - ${error.message}`; const eventId = this.auditService.logEvent( AuditEventType.TRANSACTION_FAILED, message, AuditSeverity.HIGH, { correlationId, transactionId, source: 'transaction-trace-logger' }, { transactionId, error: { name: error.name, message: error.message, stack: error.stack }, context, timestamp: Date.now() } ); this.logger.error(`Transaction failed: ${transactionId}`, { eventId, correlationId, transactionId, error: error.message }); return eventId; } /** * Get transaction trace by ID */ public getTransactionTrace(transactionId: string): TransactionTrace | undefined { return this.activeTraces.get(transactionId); } /** * Get all transaction events for a correlation ID */ public getTransactionEvents(correlationId: string): any[] { const allEvents = this.auditService.getAllEvents(); return allEvents.filter(event => event.context && event.context.correlationId === correlationId && typeof event.type === 'string' && event.type.startsWith('transaction_') ); } /** * Get transaction trace summary */ public getTransactionSummary(transactionId: string): any { const trace = this.getTransactionTrace(transactionId); if (!trace) { return null; } const events = this.getTransactionEvents(trace.correlationId); const completedSteps = trace.steps.filter(s => s.status === 'completed'); const failedSteps = trace.steps.filter(s => s.status === 'failed'); return { transactionId, correlationId: trace.correlationId, status: trace.status, startTime: trace.startTime, endTime: trace.endTime, totalDuration: trace.endTime ? trace.endTime - trace.startTime : undefined, totalSteps: trace.steps.length, completedSteps: completedSteps.length, failedSteps: failedSteps.length, totalEvents: events.length, steps: trace.steps, metadata: trace.metadata }; } /** * Get all active transaction traces */ public getActiveTraces(): TransactionTrace[] { return Array.from(this.activeTraces.values()); } /** * Get all transaction traces (including completed ones) */ public getTraces(): any[] { const allEvents = this.auditService.getAllEvents(); const completedTraces = new Map<string, any>(); // Find all transaction completion events allEvents.forEach(event => { if (event.type === AuditEventType.TRANSACTION_COMPLETED || event.type === AuditEventType.TRANSACTION_FAILED) { const transactionId = event.context.transactionId; if (!transactionId) return; // Skip if transactionId is undefined const traceData = event.data; completedTraces.set(transactionId, { transactionId, status: traceData.status, startTime: traceData.startTime, endTime: traceData.endTime, totalDuration: traceData.totalDuration, totalSteps: traceData.totalSteps, summary: traceData.summary, metadata: traceData.metadata, steps: traceData.steps, correlationId: event.context.correlationId }); } }); return Array.from(completedTraces.values()); } /** * Export transaction traces with filtering */ public async exportTraces(filters?: { status?: string; startTime?: number; endTime?: number; transactionId?: string; }): Promise<any[]> { const allTraces = this.getTraces(); return allTraces.filter(trace => { if (filters?.status && trace.status !== filters.status) { return false; } if (filters?.startTime && trace.startTime < filters.startTime) { return false; } if (filters?.endTime && trace.endTime > filters.endTime) { return false; } if (filters?.transactionId && trace.transactionId !== filters.transactionId) { return false; } return true; }); } private mapTransactionStatusToSeverity(status: string): AuditSeverity { switch (status) { case 'failed': return AuditSeverity.HIGH; case 'cancelled': return AuditSeverity.MEDIUM; case 'completed': default: return AuditSeverity.LOW; } } private mapStatusToEventType(status: string): AuditEventType { switch (status) { case 'completed': return AuditEventType.TRANSACTION_COMPLETED; case 'failed': return AuditEventType.TRANSACTION_FAILED; case 'cancelled': return AuditEventType.TRANSACTION_FAILED; // Treat cancelled as failed for audit purposes default: return AuditEventType.TRANSACTION_TRACE; } } }

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/evilpixi/pixi-midnight-mcp'

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