@cyanheads/jinaai-mcp-server
Version:
A Model Context Protocol (MCP) server that provides intelligent web reading capabilities using the Jina AI Reader API. It extracts clean, LLM-ready content from any URL.
179 lines • 7.54 kB
JavaScript
/**
* @fileoverview OpenTelemetry SDK initialization and lifecycle management.
* This file MUST be imported before any other module in the application's
* entry point (`src/index.ts`) to ensure all modules are correctly instrumented.
* It handles both the initialization (startup) and graceful shutdown of the SDK.
* @module src/utils/telemetry/instrumentation
*/
import { DiagConsoleLogger, DiagLogLevel, diag } from "@opentelemetry/api";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { WinstonInstrumentation } from "@opentelemetry/instrumentation-winston";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { BatchSpanProcessor, TraceIdRatioBasedSampler, } from "@opentelemetry/sdk-trace-node";
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions/incubating";
import path from "path";
import winston from "winston";
import { config } from "../../config/index.js";
export let sdk = null;
if (config.openTelemetry.enabled) {
// --- Custom Diagnostic Logger for OpenTelemetry ---
class OtelDiagnosticLogger extends DiagConsoleLogger {
winstonLogger;
constructor(logLevel) {
super();
const logsDir = config.logsPath;
if (!logsDir) {
if (process.stdout.isTTY) {
console.error("OpenTelemetry Diagnostics: Log directory not available. Diagnostics will be written to console only.");
}
this.winstonLogger = winston.createLogger({
level: DiagLogLevel[logLevel].toLowerCase(),
transports: [new winston.transports.Console()],
});
return;
}
this.winstonLogger = winston.createLogger({
level: DiagLogLevel[logLevel].toLowerCase(),
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
transports: [
new winston.transports.File({
filename: path.join(logsDir, "opentelemetry.log"),
maxsize: 5 * 1024 * 1024,
maxFiles: 3,
}),
],
});
}
error = (message, ...args) => {
this.winstonLogger.error(message, { args });
};
warn = (message, ...args) => {
this.winstonLogger.warn(message, { args });
};
info = (message, ...args) => {
this.winstonLogger.info(message, { args });
};
debug = (message, ...args) => {
this.winstonLogger.debug(message, { args });
};
verbose = (message, ...args) => {
this.winstonLogger.verbose(message, { args });
};
}
/**
* A custom SpanProcessor that writes ended spans to a log file using Winston.
*/
class FileSpanProcessor {
traceLogger;
constructor() {
const logsDir = config.logsPath;
if (!logsDir) {
diag.error("[FileSpanProcessor] Cannot initialize: logsPath is not available.");
this.traceLogger = winston.createLogger({ silent: true });
return;
}
this.traceLogger = winston.createLogger({
format: winston.format.json(),
transports: [
new winston.transports.File({
filename: path.join(logsDir, "traces.log"),
maxsize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
}),
],
});
}
forceFlush() {
return Promise.resolve();
}
onStart(_span) { }
onEnd(span) {
const loggableSpan = {
traceId: span.spanContext().traceId,
spanId: span.spanContext().spanId,
name: span.name,
kind: span.kind,
startTime: span.startTime,
endTime: span.endTime,
duration: span.duration,
status: span.status,
attributes: span.attributes,
events: span.events,
};
this.traceLogger.info(loggableSpan);
}
shutdown() {
return new Promise((resolve) => this.traceLogger.on("finish", resolve).end());
}
}
try {
const otelLogLevel = DiagLogLevel[config.openTelemetry.logLevel] ?? DiagLogLevel.INFO;
diag.setLogger(new OtelDiagnosticLogger(otelLogLevel), otelLogLevel);
const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: config.openTelemetry.serviceName,
[ATTR_SERVICE_VERSION]: config.openTelemetry.serviceVersion,
"deployment.environment.name": config.environment,
});
let spanProcessor;
if (config.openTelemetry.tracesEndpoint) {
diag.info(`Using OTLP exporter for traces, endpoint: ${config.openTelemetry.tracesEndpoint}`);
const traceExporter = new OTLPTraceExporter({
url: config.openTelemetry.tracesEndpoint,
});
spanProcessor = new BatchSpanProcessor(traceExporter);
}
else {
diag.info("No OTLP endpoint configured. Using FileSpanProcessor for local trace logging.");
spanProcessor = new FileSpanProcessor();
}
const metricReader = config.openTelemetry.metricsEndpoint
? new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: config.openTelemetry.metricsEndpoint,
}),
exportIntervalMillis: 15000,
})
: undefined;
sdk = new NodeSDK({
resource,
spanProcessors: [spanProcessor],
metricReader,
sampler: new TraceIdRatioBasedSampler(config.openTelemetry.samplingRatio),
instrumentations: [
getNodeAutoInstrumentations({
"@opentelemetry/instrumentation-http": {
enabled: true,
ignoreIncomingRequestHook: (req) => req.url === "/healthz",
},
"@opentelemetry/instrumentation-fs": { enabled: false },
}),
new WinstonInstrumentation({
enabled: true,
}),
],
});
sdk.start();
diag.info(`OpenTelemetry initialized for ${config.openTelemetry.serviceName} v${config.openTelemetry.serviceVersion}`);
}
catch (error) {
diag.error("Error initializing OpenTelemetry", error);
process.exit(1);
}
}
/**
* Gracefully shuts down the OpenTelemetry SDK.
* This function is called during the application's shutdown sequence.
*/
export async function shutdownOpenTelemetry() {
if (sdk) {
await sdk
.shutdown()
.then(() => diag.info("OpenTelemetry terminated"))
.catch((error) => diag.error("Error terminating OpenTelemetry", error));
}
}
//# sourceMappingURL=instrumentation.js.map