@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
JavaScript
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);
}
}
}
}