@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
165 lines (164 loc) • 5.92 kB
JavaScript
/**
* OpenTelemetry Exporter
* Exports spans to OTLP-compatible backends
*/
import { gzipSync } from "zlib";
import { SpanSerializer } from "../utils/spanSerializer.js";
import { BaseExporter } from "./baseExporter.js";
/**
* OpenTelemetry exporter for OTLP-compatible backends
* Supports HTTP, gRPC, and Zipkin protocols
*/
export class OtelExporter extends BaseExporter {
endpoint;
protocol;
serviceName;
serviceVersion;
resourceAttributes;
compression;
constructor(config) {
super("opentelemetry", config);
this.endpoint = config.endpoint;
this.protocol = config.protocol ?? "http";
this.serviceName = config.serviceName ?? "neurolink-ai";
this.serviceVersion = config.serviceVersion ?? "1.0.0";
this.resourceAttributes = config.resourceAttributes ?? {};
this.compression = config.compression ?? "none";
}
async initialize() {
if (this.initialized) {
return;
}
this.initialized = true;
this.startFlushInterval(this.config.flushIntervalMs ?? 5000);
}
async exportSpan(span) {
// Intentionally buffer spans rather than exporting immediately.
// OTLP is designed for batch export (resourceSpans envelope), so we
// accumulate spans and flush them together via exportBatch() on the
// configured flushInterval or when the buffer reaches maxBufferSize.
this.bufferSpan(span);
return this.createSuccessResult(0, 0);
}
async exportBatch(spans) {
const startTime = Date.now();
try {
const otelSpans = spans.map((s) => SpanSerializer.toOtelFormat(s));
const payload = {
resourceSpans: [
{
resource: {
attributes: [
{
key: "service.name",
value: { stringValue: this.serviceName },
},
{
key: "service.version",
value: { stringValue: this.serviceVersion },
},
...Object.entries(this.resourceAttributes).map(([key, value]) => ({
key,
value: { stringValue: value },
})),
],
},
scopeSpans: [
{
scope: {
name: "neurolink-observability",
version: "1.0.0",
},
spans: otelSpans,
},
],
},
],
};
const url = this.getExportUrl();
await this.sendRequest(url, payload);
return this.createSuccessResult(spans.length, Date.now() - startTime);
}
catch (error) {
return this.createFailureResult(spans.map((s) => s.spanId), error instanceof Error ? error.message : String(error), Date.now() - startTime);
}
}
async flush() {
if (this.buffer.length > 0) {
const spans = [...this.buffer];
this.buffer = [];
await this.exportBatch(spans);
}
}
async shutdown() {
await this.flush();
this.stopFlushInterval();
this.initialized = false;
}
async healthCheck() {
try {
await this.withRetry(() => this.ping(), "health check");
return this.createHealthStatus(true);
}
catch {
return this.createHealthStatus(false, ["Endpoint unreachable"]);
}
}
/**
* Verify connectivity to OTLP endpoint
*/
async ping() {
const response = await fetch(this.endpoint, { method: "HEAD" });
// 405 (Method Not Allowed) is acceptable for HEAD requests
if (!response.ok && response.status !== 405) {
throw new Error(`OTLP endpoint unreachable: ${response.status}`);
}
}
/**
* Get the export URL based on protocol
*/
getExportUrl() {
switch (this.protocol) {
case "http":
return `${this.endpoint}/v1/traces`;
case "zipkin":
return `${this.endpoint}/api/v2/spans`;
case "grpc":
// For gRPC, this would use @grpc/grpc-js
return this.endpoint;
default:
return `${this.endpoint}/v1/traces`;
}
}
/**
* Send request with optional gzip compression
* @param endpoint - The URL to send to
* @param body - The payload to send
*/
async sendRequest(endpoint, body) {
const jsonBody = JSON.stringify(body);
let bodyData = jsonBody;
const headers = {
"Content-Type": "application/json",
};
// Apply gzip compression if configured
if (this.compression === "gzip") {
const compressed = gzipSync(Buffer.from(jsonBody));
// Convert Buffer to Uint8Array for fetch compatibility
bodyData = new Uint8Array(compressed);
headers["Content-Encoding"] = "gzip";
}
// Add any custom headers from config
if (this.config.headers) {
Object.assign(headers, this.config.headers);
}
const response = await fetch(endpoint, {
method: "POST",
headers,
body: bodyData,
});
if (!response.ok) {
throw new Error(`OTLP export failed: ${response.statusText}`);
}
}
}