Files
apf_portal/infra/local/otel-collector.yaml
T
Julien Gautier 13a12e6591
CI / commits (pull_request) Successful in 1m9s
CI / scan (pull_request) Successful in 1m21s
CI / check (pull_request) Successful in 1m35s
CI / a11y (pull_request) Successful in 1m25s
CI / perf (pull_request) Successful in 2m38s
feat(portal-shell): wire spa-side opentelemetry tracing
Phase 2 of ADR-0012 — closes the loop SPA → BFF → DB. With this
PR a single user action (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, and (eventually)
Redis / downstream-API hops.

Runtime libs added (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

Code:

- apps/portal-shell/src/observability/tracing.ts — WebTracerProvider
  bootstrap. Documents the load-order constraint inline (must be
  the very first import of main.ts, same pattern as the BFF). No
  context-zone package because the workspace is zoneless (per
  ADR-0004); the default StackContextManager covers the
  auto-instrumentation cases.
- main.ts now imports the tracing module as line 1.

CORS plumbing for end-to-end 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 and tracestate headers. The
  full security-grade CORS belongs to phase-2 security ADR; this
  is the strict minimum for the SPA→BFF link to keep the trace
  context across origins.
- OTel Collector (infra/local/otel-collector.yaml) gains a cors
  block on its OTLP/HTTP receiver so the browser's OTLP POST
  clears its own pre-flight.

ADR-0012 §Confirmation: a new "Wired in the SPA foundation PR
(phase 2)" block enumerates what landed here; the carry-over
"Wired as features land" list is updated to drop the SPA-side
SDK item that used to live there and to add a CORS-grade
follow-up note.

Verified locally:
- pnpm exec nx run-many -t lint test build → 8 projects green.
- pnpm audit clean.
- After ./infra/local/dev.sh up observability and nx serve
  portal-shell, opening http://localhost:4200 produces a
  document_load trace in Jaeger with the SPA service.name; a
  manual fetch from DevTools to /api/health on the BFF produces
  a child span on the same trace.
2026-05-09 23:18:42 +02:00

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