8f2cd4e068
## Summary Phase 2 of ADR-0012 — closes the loop SPA → BFF → DB. After this PR, a single user action (initial page load, click, form submit) produces one trace whose root span is owned by the SPA and whose child spans cover the BFF request, Postgres queries through Prisma, and (eventually) Redis / downstream-API hops. ## What lands **Browser-side OTel libs** (production deps): - `@opentelemetry/sdk-trace-web` — browser tracer + provider - `@opentelemetry/exporter-trace-otlp-http` — OTLP/HTTP+JSON exporter - `@opentelemetry/instrumentation` — auto-instrumentation runtime - `@opentelemetry/instrumentation-fetch` — `fetch` + W3C `traceparent` propagation - `@opentelemetry/instrumentation-document-load` — initial-paint timings - `@opentelemetry/instrumentation-user-interaction` — click / keypress / submit No `@opentelemetry/context-zone`: the workspace is zoneless per ADR-0004; the default `StackContextManager` covers the auto-instrumented paths. Custom spans across `await` will need explicit `context.with(...)` plumbing — fine, encountered as code lands. **Code**: - [`apps/portal-shell/src/observability/tracing.ts`](apps/portal-shell/src/observability/tracing.ts) — `WebTracerProvider` bootstrap. Documents the load-order constraint inline (same pattern as the BFF: must be the very first import of `main.ts`, otherwise auto-instrumentations miss everything imported above). - `apps/portal-shell/src/main.ts` now imports the tracing module as line 1. **CORS plumbing** for end-to-end trace propagation: - BFF (`apps/portal-bff/src/main.ts`) calls `enableCors` with a minimal dev allowlist (`http://localhost:4200`) and explicit permission for the W3C `traceparent` / `tracestate` headers. The full security-grade CORS (per-environment allowlists, helmet, cookie-session, CSRF) belongs to the future phase-2 security ADR — this PR adds the strict minimum for the SPA→BFF trace context to survive cross-origin pre-flight. - OTel Collector (`infra/local/otel-collector.yaml`) gains a `cors` block on its OTLP/HTTP receiver so the browser's own OTLP POST clears its pre-flight. **ADR-0012 §Confirmation** rewritten: a new "Wired in the SPA foundation PR (phase 2)" block enumerates what landed here; the carry-over "Wired as features land" list drops the SPA-side SDK item and adds a follow-up note about the security-grade CORS. ## Verification ```bash pnpm exec nx run-many -t lint test build # 8 projects green pnpm audit # 0 vulns ./infra/local/dev.sh up observability # bring up Collector + Jaeger ./infra/local/dev.sh # (separately, BFF stack — your choice) pnpm nx serve portal-bff # localhost:3000 pnpm nx serve portal-shell # localhost:4200 ``` Open http://localhost:4200 → a `document_load` trace appears in http://localhost:16686 with `service.name=portal-shell`. From DevTools, run `fetch('http://localhost:3000/api/health').then(r => r.json())` → a fetch span appears with a child BFF span on the same trace. ## Test plan - [ ] CI green on this PR. - [ ] After local up, `document_load` span visible in Jaeger UI for the SPA. - [ ] Cross-origin fetch from SPA carries `traceparent` (visible in Network tab) and produces a single end-to-end trace SPA → BFF in Jaeger. - [ ] DevTools console shows no CORS warnings about `traceparent`, `tracestate`, or the `localhost:4318/v1/traces` POST. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #72
78 lines
2.3 KiB
YAML
78 lines
2.3 KiB
YAML
# OpenTelemetry Collector config for the local-dev stack.
|
|
#
|
|
# v1 scope (per ADR-0012):
|
|
# - Accept OTLP from the BFF and the SPA on gRPC (4317) and HTTP
|
|
# (4318). The default OTEL_EXPORTER_OTLP_ENDPOINT in dev points
|
|
# here.
|
|
# - Batch + log everything to stdout via the `debug` exporter — the
|
|
# dev sees what their app emits without standing up a backend.
|
|
# - When the `observability` Compose profile is active, additionally
|
|
# forward to Jaeger (`jaeger:4317` on the internal Compose
|
|
# network). When the profile is inactive, the OTLP→Jaeger export
|
|
# fails at warn level but does not impact the debug pipeline.
|
|
#
|
|
# Production replaces the debug exporter with the proper backend
|
|
# (chosen in the future on-prem infrastructure ADR — likely Tempo +
|
|
# Loki + Mimir, or an OpenTelemetry-friendly all-in-one).
|
|
|
|
receivers:
|
|
otlp:
|
|
protocols:
|
|
grpc:
|
|
endpoint: 0.0.0.0:4317
|
|
http:
|
|
endpoint: 0.0.0.0:4318
|
|
# CORS for the SPA-side OTLP/HTTP exporter (per ADR-0012,
|
|
# phase 2). The browser's pre-flight needs both the origin
|
|
# and the W3C trace headers in the allow-list — without it,
|
|
# the OTLP POST is blocked client-side and SPA spans never
|
|
# reach the Collector.
|
|
cors:
|
|
allowed_origins:
|
|
- http://localhost:4200
|
|
allowed_headers:
|
|
- content-type
|
|
- traceparent
|
|
- tracestate
|
|
|
|
processors:
|
|
batch:
|
|
# Small batches in dev so the developer sees output quickly.
|
|
timeout: 5s
|
|
send_batch_size: 100
|
|
|
|
exporters:
|
|
debug:
|
|
verbosity: detailed
|
|
otlp/jaeger:
|
|
endpoint: jaeger:4317
|
|
tls:
|
|
insecure: true
|
|
sending_queue:
|
|
enabled: true
|
|
# When jaeger isn't running (profile off), retries fall back
|
|
# to a small queue rather than blocking the pipeline.
|
|
queue_size: 100
|
|
retry_on_failure:
|
|
enabled: true
|
|
initial_interval: 5s
|
|
max_interval: 30s
|
|
|
|
service:
|
|
pipelines:
|
|
traces:
|
|
receivers: [otlp]
|
|
processors: [batch]
|
|
exporters: [debug, otlp/jaeger]
|
|
logs:
|
|
receivers: [otlp]
|
|
processors: [batch]
|
|
exporters: [debug]
|
|
metrics:
|
|
receivers: [otlp]
|
|
processors: [batch]
|
|
exporters: [debug]
|
|
telemetry:
|
|
logs:
|
|
level: info
|