docs: refresh architecture references for OTel migration
This commit is contained in:
@@ -94,18 +94,21 @@ Three layers work in tandem:
|
||||
|
||||
The two enforcement layers are independent but complementary. ESLint is stricter on per-import context (e.g., file-specific exemptions via `// @boundaries-ignore`), while Turborepo catches transitive issues that lint-time checking misses. Run `pnpm lint` and `pnpm turbo boundaries` in CI to catch all violations.
|
||||
|
||||
## TRACER / LOGGER (Plan 10)
|
||||
## TRACER / LOGGER / METRICS (ADR-014, ADR-017)
|
||||
|
||||
The instrumentation layer is **per-feature container** but **app-wide instance**: each feature container binds `INSTRUMENTATION_SYMBOLS.TRACER` and `INSTRUMENTATION_SYMBOLS.LOGGER` to the SAME instance, constructed once by the app's `bindAll()` dispatcher (Rule 0).
|
||||
The instrumentation layer is **per-feature container** but **app-wide instance**: each feature container binds `INSTRUMENTATION_SYMBOLS.ITracer`, `INSTRUMENTATION_SYMBOLS.ILogger`, and `INSTRUMENTATION_SYMBOLS.IMetrics` to the SAME instances, constructed once by the app's `bindAll()` dispatcher (Rule 0).
|
||||
|
||||
**Substrate:** OpenTelemetry SDK. Sentry is the exporter via `@sentry/opentelemetry`. PII scrubbing runs at the OTel processor layer (`PiiScrubSpanProcessor` + `PiiScrubLogRecordProcessor`) before the Sentry exporter sees the data. Feature code is never coupled to the OTel SDK or Sentry SDK directly (ADR-017).
|
||||
|
||||
```
|
||||
apps/web-next/src/server/bind-production.ts (bindAll)
|
||||
│
|
||||
├─ Rule 0: WEB_NEXT_SENTRY_DSN set?
|
||||
│ yes → bindSentryInstrumentation(sharedContainer, { dsn, app: "web-next" })
|
||||
│ yes → bindOtelInstrumentation(sharedContainer, { dsn, app: "web-next" })
|
||||
│ → initOtelServerNode(dsn, ...) → OTel SDK + Sentry exporter + PII scrub processors
|
||||
│ no → bindNoopInstrumentation(sharedContainer)
|
||||
│ ↓
|
||||
│ tracer + logger instances
|
||||
│ tracer (OtelTracer) + logger (OtelLogger) + metrics (OtelMetrics) instances
|
||||
│ ↓
|
||||
├─ resolveEventsAndJobs* → IEventBus + IJobQueue (ADR-015)
|
||||
│ production → PayloadJobsEventBus + PayloadJobQueue
|
||||
@@ -115,9 +118,9 @@ apps/web-next/src/server/bind-production.ts (bindAll)
|
||||
│ server.ts → SocketIORealtimeBroadcaster + RealtimeHandlerRegistry (passed in from server.ts)
|
||||
│ page/test → InMemoryRealtimeBroadcaster + RealtimeHandlerRegistry (defaults)
|
||||
│ ↓
|
||||
├─ build ctx: BindProductionContext = { config, tracer, logger, bus, queue, realtime, realtimeRegistry }
|
||||
├─ build ctx: BindProductionContext = { config, tracer, logger, metrics?, bus, queue, realtime, realtimeRegistry }
|
||||
│ Required: tracer, logger, config (production only)
|
||||
│ Optional: bus, queue, realtime, realtimeRegistry (guard with ?. when used)
|
||||
│ Optional: metrics, bus, queue, realtime, realtimeRegistry (guard with ?. when used)
|
||||
│ ↓
|
||||
├─ bindProductionBlog(ctx: BindProductionContext)
|
||||
│ │
|
||||
@@ -138,8 +141,9 @@ apps/web-next/src/server/bind-production.ts (bindAll)
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `tracer` | `ITracer` | always | Resolved by Rule 0 (Sentry vs Noop) |
|
||||
| `tracer` | `ITracer` | always | Resolved by Rule 0 (OTel+Sentry vs Noop) |
|
||||
| `logger` | `ILogger` | always | Resolved by Rule 0 |
|
||||
| `metrics` | `MetricsProtocol?` | optional | Resolved by Rule 0; per-feature adoption is opportunistic |
|
||||
| `config` | `SanitizedConfig` | production only | Present in `BindProductionContext`, absent in `BindContext` |
|
||||
| `bus` | `EventBusProtocol?` | optional | `IEventBus` at the aggregator; protocol surface at binders |
|
||||
| `queue` | `IJobQueue?` | optional | Present when `core-shared/jobs` is wired |
|
||||
@@ -150,6 +154,6 @@ Feature binders destructure `ctx` and use optional fields with `?.` or cast to t
|
||||
|
||||
**Why per-feature containers also get the binding:** lets internal DI-resolved code in a feature pull TRACER/LOGGER without going through the app dispatcher. In practice, only repository classes and feature-internal services would use this — controllers and use cases receive instrumentation via the bind-time wrapper.
|
||||
|
||||
**Why the shared container exists at all:** isolates Rule 0 resolution from feature containers. Feature containers don't need to know if Sentry is on or off — they just receive an `ITracer` instance.
|
||||
**Why the shared container exists at all:** isolates Rule 0 resolution from feature containers. Feature containers don't need to know if Sentry is the exporter or not — they just receive an `ITracer` instance.
|
||||
|
||||
**Boundary rule:** feature packages MUST NOT import `@sentry/*` directly (R40, ESLint-enforced). The only paths that may import the SDK are `core-shared/instrumentation/sentry/**`, the `bind-sentry-instrumentation` files, the `no-sentry` test guards, and per-app `instrumentation*.{ts,mjs}` / `next.config.{mjs}` / `vite.config.{ts}` entries.
|
||||
**Boundary rule:** feature packages MUST NOT import `@sentry/*` or `@opentelemetry/sdk-*` directly (R40 + R52, ESLint-enforced). The OTel bridge (`otel/sentry-bridge.ts`), browser init files (`sentry/init-client*.ts`), and app-level `instrumentation*.{ts,mjs}` / `next.config.{mjs}` / `vite.config.{ts}` entries are the only allowlisted paths.
|
||||
|
||||
Reference in New Issue
Block a user