2026-06-15 12:53:06 +08:00
|
|
|
import { createRequire } from "node:module";
|
|
|
|
|
import type { SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
|
|
|
|
|
|
|
|
let _active = false;
|
|
|
|
|
let _sdk: { shutdown(): Promise<void> } | null = null;
|
|
|
|
|
|
|
|
|
|
export function isTracingActive(): boolean {
|
|
|
|
|
return _active;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function initTracing(options: { exporter?: SpanExporter } = {}): Promise<void> {
|
|
|
|
|
if (_active) return;
|
|
|
|
|
|
|
|
|
|
const { NodeSDK } = await import("@opentelemetry/sdk-node");
|
|
|
|
|
const { BatchSpanProcessor } = await import("@opentelemetry/sdk-trace-base");
|
|
|
|
|
const { OTLPTraceExporter } = await import("@opentelemetry/exporter-trace-otlp-http");
|
|
|
|
|
const { defaultResource, resourceFromAttributes } = await import("@opentelemetry/resources");
|
|
|
|
|
const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } = await import(
|
|
|
|
|
"@opentelemetry/semantic-conventions"
|
|
|
|
|
);
|
|
|
|
|
const { HttpInstrumentation } = await import("@opentelemetry/instrumentation-http");
|
|
|
|
|
const { FastifyInstrumentation } = await import("@opentelemetry/instrumentation-fastify");
|
|
|
|
|
const { PgInstrumentation } = await import("@opentelemetry/instrumentation-pg");
|
|
|
|
|
const { IORedisInstrumentation } = await import("@opentelemetry/instrumentation-ioredis");
|
|
|
|
|
const { AwsInstrumentation } = await import("@opentelemetry/instrumentation-aws-sdk");
|
|
|
|
|
|
|
|
|
|
const require = createRequire(import.meta.url);
|
|
|
|
|
const { version } = require("../package.json");
|
|
|
|
|
|
|
|
|
|
const exporter = options.exporter ?? new OTLPTraceExporter();
|
|
|
|
|
|
|
|
|
|
const resource = defaultResource().merge(
|
|
|
|
|
resourceFromAttributes({
|
|
|
|
|
[ATTR_SERVICE_NAME]: "snapotter-api",
|
|
|
|
|
[ATTR_SERVICE_VERSION]: version,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const sdk = new NodeSDK({
|
|
|
|
|
resource,
|
|
|
|
|
spanProcessors: [new BatchSpanProcessor(exporter)],
|
|
|
|
|
instrumentations: [
|
|
|
|
|
new HttpInstrumentation({
|
|
|
|
|
ignoreOutgoingRequestHook: (req) => {
|
|
|
|
|
const host = req.hostname || req.host || "";
|
|
|
|
|
return host.includes("posthog") || host.includes("sentry");
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
new FastifyInstrumentation(),
|
|
|
|
|
new PgInstrumentation(),
|
|
|
|
|
new IORedisInstrumentation({
|
|
|
|
|
requireParentSpan: true,
|
|
|
|
|
dbStatementSerializer: (cmd, args) => {
|
|
|
|
|
if (cmd === "evalsha" || cmd === "eval") return `${cmd} <lua>`;
|
|
|
|
|
return `${cmd} ${(args ?? []).slice(0, 2).join(" ")}`;
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
new AwsInstrumentation(),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
sdk.start();
|
|
|
|
|
_sdk = sdk;
|
|
|
|
|
_active = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function shutdownTracing(): Promise<void> {
|
|
|
|
|
if (_sdk) {
|
|
|
|
|
await _sdk.shutdown().catch(() => {});
|
|
|
|
|
_sdk = null;
|
|
|
|
|
_active = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Preload entry point --
|
|
|
|
|
// When loaded via --import, this top-level await runs before the app.
|
|
|
|
|
|
|
|
|
|
const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
|
|
|
if (endpoint) {
|
|
|
|
|
try {
|
|
|
|
|
const enterprise = await import("@snapotter/enterprise");
|
2026-06-24 17:27:59 +08:00
|
|
|
// The rest of the app reads SNAPOTTER_LICENSE_KEY (env.ts / index.ts). Accept
|
|
|
|
|
// either here so distributed_tracing activates with the same key as every other
|
|
|
|
|
// enterprise feature; LICENSE_KEY kept as an override for preload-only setups.
|
|
|
|
|
const licenseKey = process.env.LICENSE_KEY ?? process.env.SNAPOTTER_LICENSE_KEY ?? "";
|
2026-06-15 12:53:06 +08:00
|
|
|
if (licenseKey) {
|
|
|
|
|
enterprise.initEnterprise(licenseKey);
|
|
|
|
|
}
|
|
|
|
|
if (enterprise.isFeatureEnabled("distributed_tracing")) {
|
|
|
|
|
await initTracing();
|
|
|
|
|
console.log("[tracing] OpenTelemetry initialized, exporting to", endpoint);
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// Enterprise package not available or license invalid -- tracing stays off
|
|
|
|
|
}
|
|
|
|
|
}
|