@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
387 lines • 14.6 kB
JavaScript
import { context, metrics, trace, } from "@opentelemetry/api";
import { BasicTracerProvider, BatchSpanProcessor, } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } 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;
tracerProvider;
enabled = false;
initialized = false;
usingExternalTracerProvider = false;
meter;
tracer;
// Optional Metrics (only created when enabled)
aiRequestCounter;
aiRequestDuration;
aiTokensUsed;
aiProviderErrors;
aiCostUsd;
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 (this.hasExternalTracerProvider() ||
process.env.NEUROLINK_TELEMETRY_ENABLED === "true" ||
process.env.OTEL_EXPORTER_OTLP_ENDPOINT !== undefined);
}
hasExternalTracerProvider() {
try {
const provider = trace.getTracerProvider();
if (!provider) {
return false;
}
const providerName = provider.constructor?.name || "";
if (providerName &&
providerName !== "ProxyTracerProvider" &&
providerName !== "NoopTracerProvider") {
return true;
}
const delegate = typeof provider.getDelegate === "function"
? provider.getDelegate()
: provider._delegate;
const delegateName = delegate?.constructor?.name || "";
return Boolean(delegateName && delegateName !== "NoopTracerProvider");
}
catch (error) {
logger.warn("[Telemetry] Failed checking for external TracerProvider", {
error: error instanceof Error ? error.message : String(error),
});
return false;
}
}
adoptExternalTracerProvider(reason) {
this.usingExternalTracerProvider = true;
this.tracerProvider = undefined;
this.meter = metrics.getMeter("neurolink-ai");
this.tracer = trace.getTracer("neurolink-ai");
this.initializeMetrics();
logger.debug("[Telemetry] Reusing externally managed TracerProvider", {
reason,
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
});
}
initializeTelemetry() {
try {
if (this.hasExternalTracerProvider()) {
this.adoptExternalTracerProvider("global tracer provider already registered");
return;
}
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
[ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1",
});
const exporter = new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`
: undefined,
});
this.tracerProvider = new BasicTracerProvider({
resource,
spanProcessors: [new BatchSpanProcessor(exporter)],
});
this.meter = metrics.getMeter("neurolink-ai");
this.tracer = this.tracerProvider.getTracer("neurolink-ai");
this.initializeMetrics();
logger.debug("[Telemetry] Initialized local telemetry exporter", {
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
globalTracerProviderOwnedBy: "observability/instrumentation",
});
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const isDuplicateRegistration = errorMessage.includes("duplicate registration") ||
errorMessage.includes("already registered") ||
errorMessage.includes("already set");
if (isDuplicateRegistration && this.hasExternalTracerProvider()) {
this.adoptExternalTracerProvider("duplicate global tracer registration detected");
return;
}
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.aiCostUsd = this.meter.createCounter("ai_cost_usd_total", {
description: "Total accumulated AI cost in USD",
});
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;
}
if (this.usingExternalTracerProvider) {
this.initialized = true;
logger.debug("[Telemetry] External TracerProvider already initialized by host");
return;
}
if (!this.tracerProvider) {
this.initialized = true;
logger.debug("[Telemetry] Tracer provider already prepared during constructor");
return;
}
try {
// Register AsyncLocalStorage context manager for proper parent-child
// span relationships across async boundaries (required for startActiveSpan)
try {
const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks");
context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
}
catch {
// context-async-hooks not installed — context propagation
// will use the default (noop) manager
}
this.initialized = true;
logger.debug("[Telemetry] Tracer provider started successfully");
}
catch (error) {
logger.error("[Telemetry] Failed to start:", error);
this.enabled = false;
this.initialized = false;
}
}
// AI Operation Tracing (NO-OP when disabled)
/**
* @deprecated Vercel AI SDK's experimental_telemetry creates ai.generateText/ai.streamText
* spans automatically via OpenTelemetry. Using this method would create duplicate spans.
* Kept for potential future use with non-Vercel providers (e.g., Amazon Bedrock).
* See: TelemetryHandler.getTelemetryConfig() for the active telemetry path.
*/
async traceAIRequest(provider, operation, operationType = "generate_text") {
if (!this.enabled || !this.tracer) {
return await operation();
}
const span = this.tracer.startSpan(`ai.${provider}.${operationType}`, {
attributes: {
"ai.provider": provider,
"ai.operation": operationType,
},
});
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, cost) {
// 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);
if (cost !== undefined && Number.isFinite(cost) && cost > 0) {
this.aiCostUsd?.add(cost, 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,
initialized: this.initialized,
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.tracerProvider &&
!this.usingExternalTracerProvider) {
try {
await this.tracerProvider.shutdown();
this.initialized = false;
logger.debug("[Telemetry] Tracer provider shutdown completed");
}
catch (error) {
logger.error("[Telemetry] Error during shutdown:", error);
}
}
}
}
//# sourceMappingURL=telemetryService.js.map