/** * OpenTelemetry SDK bootstrap for the SPA (per ADR-0012, phase 2). * * Symmetrical to apps/portal-bff/src/observability/tracing.ts — must * be the very first import in `main.ts` so the auto-instrumentations * patch `fetch` / `XMLHttpRequest` / `addEventListener` before any * application code runs. * * What this enables out of the box * ──────────────────────────────── * - `document_load` span on first paint, capturing every Performance * Timing API metric (DNS, TLS, request, response, DOM events). * - `fetch` spans for every outgoing request, with the W3C * `traceparent` header propagated so the BFF picks up the same * trace id and produces a child span. The result in Jaeger is one * end-to-end trace SPA → BFF → DB. * - `user_interaction` spans for click / keypress events on * instrumented elements. * * Notes on the zoneless setup * ─────────────────────────── * Angular here is zoneless (per ADR-0004), so we deliberately do NOT * pull in `@opentelemetry/context-zone`. The default * `StackContextManager` baked into `@opentelemetry/sdk-trace-web` is * sufficient: the auto-instrumentations capture context at patch * time and propagate it across the boundaries they own (fetch * lifecycle, event handlers). Custom spans across `await` will need * explicit `context.with(...)` plumbing — fine, encountered as code * lands. * * Endpoint configuration * ────────────────────── * The OTLP/HTTP endpoint and the BFF origin both come from * `src/environments/environment.ts` (per ADR-0018). Per-environment * siblings of that file ship under `fileReplacements`. */ import { WebTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-web'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { resourceFromAttributes } from '@opentelemetry/resources'; import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction'; import { environment } from '../environments/environment'; const SERVICE_NAME = 'portal-shell'; // Static for now; a follow-up wires this to the build-time version // (same source as the footer's dev-only version badge). const SERVICE_VERSION = 'dev'; // Derive the trace-header propagation pattern from the BFF base URL // so a deploy-time change to `bffApiBaseUrl` automatically propagates // `traceparent` to the right origin. RegExp special chars are escaped // before going into the source. const bffOrigin = new URL(environment.bffApiBaseUrl).origin; const bffOriginRegex = new RegExp(`^${bffOrigin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/.*`); const provider = new WebTracerProvider({ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: SERVICE_NAME, [ATTR_SERVICE_VERSION]: SERVICE_VERSION, }), spanProcessors: [ new BatchSpanProcessor(new OTLPTraceExporter({ url: environment.otlpEndpoint })), ], }); provider.register(); registerInstrumentations({ instrumentations: [ // Times the initial page load — fires once. new DocumentLoadInstrumentation(), // Wraps every `fetch` call. Propagates `traceparent` to the BFF // origin derived from the environment. new FetchInstrumentation({ propagateTraceHeaderCorsUrls: [bffOriginRegex], }), // Auto-spans on click / keypress / submit (and a small allow-list // beyond that). Quiet in dev, useful in staging perf // investigations. new UserInteractionInstrumentation(), ], });