UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and

296 lines (295 loc) 10.3 kB
import { NodeSDK } from "@opentelemetry/sdk-node"; import { metrics, trace, } from "@opentelemetry/api"; import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; import { Resource } from "@opentelemetry/resources"; import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions"; import { logger } from "../utils/logger.js"; export class TelemetryService { static instance; sdk; enabled = false; meter; tracer; // Optional Metrics (only created when enabled) aiRequestCounter; aiRequestDuration; aiTokensUsed; aiProviderErrors; mcpToolCalls; connectionCounter; responseTimeHistogram; // Runtime metrics tracking activeConnectionCount = 0; errorCount = 0; requestCount = 0; totalResponseTime = 0; responseTimeCount = 0; constructor() { // Check if telemetry is enabled this.enabled = this.isTelemetryEnabled(); if (this.enabled) { this.initializeTelemetry(); } else { logger.debug("[Telemetry] Disabled - set NEUROLINK_TELEMETRY_ENABLED=true or configure OTEL_EXPORTER_OTLP_ENDPOINT to enable"); } } static getInstance() { if (!TelemetryService.instance) { TelemetryService.instance = new TelemetryService(); } return TelemetryService.instance; } isTelemetryEnabled() { return (process.env.NEUROLINK_TELEMETRY_ENABLED === "true" || process.env.OTEL_EXPORTER_OTLP_ENDPOINT !== undefined); } initializeTelemetry() { try { const resource = new Resource({ [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai", [ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1", }); this.sdk = new NodeSDK({ resource, traceExporter: new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`, }), // Note: Metric reader configured separately instrumentations: [getNodeAutoInstrumentations()], }); this.meter = metrics.getMeter("neurolink-ai"); this.tracer = trace.getTracer("neurolink-ai"); this.initializeMetrics(); logger.debug("[Telemetry] Initialized with endpoint:", process.env.OTEL_EXPORTER_OTLP_ENDPOINT); } catch (error) { logger.error("[Telemetry] Failed to initialize:", error); this.enabled = false; } } initializeMetrics() { if (!this.enabled || !this.meter) { return; } this.aiRequestCounter = this.meter.createCounter("ai_requests_total", { description: "Total number of AI requests", }); this.aiRequestDuration = this.meter.createHistogram("ai_request_duration_ms", { description: "AI request duration in milliseconds", }); this.aiTokensUsed = this.meter.createCounter("ai_tokens_used_total", { description: "Total number of AI tokens used", }); this.aiProviderErrors = this.meter.createCounter("ai_provider_errors_total", { description: "Total number of AI provider errors", }); this.mcpToolCalls = this.meter.createCounter("mcp_tool_calls_total", { description: "Total number of MCP tool calls", }); this.connectionCounter = this.meter.createCounter("connections_total", { description: "Total number of connections", }); this.responseTimeHistogram = this.meter.createHistogram("response_time_ms", { description: "Response time in milliseconds", }); } async initialize() { if (!this.enabled) { return; } try { await this.sdk?.start(); logger.debug("[Telemetry] SDK started successfully"); } catch (error) { logger.error("[Telemetry] Failed to start SDK:", error); this.enabled = false; } } // AI Operation Tracing (NO-OP when disabled) async traceAIRequest(provider, operation) { if (!this.enabled || !this.tracer) { return await operation(); // Direct execution when disabled } const span = this.tracer.startSpan(`ai.${provider}.generate_text`, { attributes: { "ai.provider": provider, "ai.operation": "generate_text", }, }); try { const result = await operation(); span.setStatus({ code: 1 }); // OK return result; } catch (error) { span.setStatus({ code: 2, message: error instanceof Error ? error.message : "Unknown error", }); // ERROR span.recordException(error); throw error; } finally { span.end(); } } // Metrics Recording (NO-OP when disabled) recordAIRequest(provider, model, tokens, duration) { // Track runtime metrics this.requestCount++; this.totalResponseTime += duration; this.responseTimeCount++; if (!this.enabled || !this.aiRequestCounter) { return; } const labels = { provider, model }; this.aiRequestCounter.add(1, labels); this.aiRequestDuration?.record(duration, labels); this.aiTokensUsed?.add(tokens, labels); } recordAIError(provider, error) { // Track runtime metrics this.errorCount++; if (!this.enabled || !this.aiProviderErrors) { return; } this.aiProviderErrors.add(1, { provider, error: error.name, message: error.message.substring(0, 100), // Limit message length }); } recordMCPToolCall(toolName, duration, success) { if (!this.enabled || !this.mcpToolCalls) { return; } this.mcpToolCalls.add(1, { tool: toolName, success: success.toString(), duration_bucket: this.getDurationBucket(duration), }); } recordConnection(type) { // Track runtime metrics this.activeConnectionCount++; if (!this.enabled || !this.connectionCounter) { return; } this.connectionCounter.add(1, { connection_type: type }); } recordConnectionClosed(type) { // Track runtime metrics this.activeConnectionCount = Math.max(0, this.activeConnectionCount - 1); if (!this.enabled || !this.connectionCounter) { return; } // Optionally record disconnection metrics if needed this.connectionCounter.add(-1, { connection_type: type, event: "disconnect", }); } recordResponseTime(endpoint, method, duration) { // Track runtime metrics this.totalResponseTime += duration; this.responseTimeCount++; if (!this.enabled || !this.responseTimeHistogram) { return; } this.responseTimeHistogram.record(duration, { endpoint, method, status_bucket: this.getStatusBucket(duration), }); } // Custom Metrics recordCustomMetric(name, value, labels) { if (!this.enabled || !this.meter) { return; } const counter = this.meter.createCounter(`custom_${name}`, { description: `Custom metric: ${name}`, }); counter.add(value, labels || {}); } recordCustomHistogram(name, value, labels) { if (!this.enabled || !this.meter) { return; } const histogram = this.meter.createHistogram(`custom_${name}_histogram`, { description: `Custom histogram: ${name}`, }); histogram.record(value, labels || {}); } // Health Checks async getHealthMetrics() { const memoryUsage = process.memoryUsage(); // Calculate error rate as percentage of errors vs total requests const errorRate = this.requestCount > 0 ? (this.errorCount / this.requestCount) * 100 : 0; // Calculate average response time const averageResponseTime = this.responseTimeCount > 0 ? this.totalResponseTime / this.responseTimeCount : 0; return { timestamp: Date.now(), memoryUsage, uptime: process.uptime(), activeConnections: this.activeConnectionCount, errorRate: Math.round(errorRate * 100) / 100, // Round to 2 decimal places averageResponseTime: Math.round(averageResponseTime * 100) / 100, // Round to 2 decimal places }; } // Telemetry Status isEnabled() { return this.enabled; } getStatus() { return { enabled: this.enabled, endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, service: process.env.OTEL_SERVICE_NAME || "neurolink-ai", version: process.env.OTEL_SERVICE_VERSION || "3.0.1", }; } // Helper methods getDurationBucket(duration) { if (duration < 100) { return "fast"; } if (duration < 500) { return "medium"; } if (duration < 1000) { return "slow"; } return "very_slow"; } getStatusBucket(duration) { if (duration < 200) { return "excellent"; } if (duration < 500) { return "good"; } if (duration < 1000) { return "acceptable"; } return "poor"; } // Cleanup async shutdown() { if (this.enabled && this.sdk) { try { await this.sdk.shutdown(); logger.debug("[Telemetry] SDK shutdown completed"); } catch (error) { logger.error("[Telemetry] Error during shutdown:", error); } } } }