feat: add diagnostics and telemetry runtime
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# VD-07: Diagnostics와 telemetry exporter 경계
|
||||
|
||||
- 상태: Accepted
|
||||
- 결정일: 2026-07-26
|
||||
- 적용 브랜치: `feature-frontend-diagnostics-telemetry-runtime`
|
||||
- 재검토: 실제 운영 sink, consent가 필요한 analytics 또는 분산 tracing provider가
|
||||
선정될 때
|
||||
|
||||
## 배경
|
||||
|
||||
기존 telemetry registry와 best-effort HTTP queue는 있었지만 운영 진단 record와
|
||||
semantic event의 책임이 하나의 telemetry port에 섞여 있었다. boot, HTTP,
|
||||
cache, storage, route와 release failure의 선언도 실제 production producer와
|
||||
완전히 연결되지 않았다. 이 상태에서는 retry attempt마다 같은 사건을 발행하거나
|
||||
raw URL, query, request body와 오류 객체가 queue에 들어갈 위험이 있다.
|
||||
|
||||
반면 skeleton 단계에는 실제 관측 vendor, endpoint의 운영 보안 정책, analytics
|
||||
consent와 보존 기간이 결정되지 않았다. 특정 SDK를 기본 번들에 설치하는 것은
|
||||
vendor 결정 전에는 안전한 기본값이 아니다.
|
||||
|
||||
## 결정
|
||||
|
||||
1. level 기반 운영 진단은 `DiagnosticsPort`, registry 기반 semantic event는
|
||||
`TelemetryPort`로 분리한다. application은 두 port의 concrete adapter나
|
||||
exporter SDK를 알지 못한다.
|
||||
2. diagnostics의 level, event ID와 context key는 닫힌 registry/allowlist다.
|
||||
telemetry도 event별 required/optional attribute와 value policy를 적용한다.
|
||||
등록되지 않은 event·context·고카디널리티 값은 전송하지 않는다.
|
||||
3. 기본 diagnostics adapter는 bounded in-memory evidence이고 telemetry는
|
||||
설정이 없으면 true no-op이다. endpoint가 있을 때만 bounded oldest-drop
|
||||
queue와 best-effort HTTP sink를 사용한다.
|
||||
4. raw path/URL/query/body/response/storage value, credential, cookie, email,
|
||||
stack과 오류 객체 전체는 context에 넣지 않는다. route ID, operation ID,
|
||||
correlation ID, release ID, error kind, status/attempt/duration bucket만
|
||||
허용한다.
|
||||
5. HTTP logical execution은 success, retry recovery, terminal failure 또는
|
||||
abort마다 `http.request.completed` diagnostics를 정확히 한 번 남긴다.
|
||||
`api.request.failed` telemetry는 retry가 끝난 terminal non-abort failure에만
|
||||
정확히 한 번 발행한다.
|
||||
6. `app.boot.failed`, `ui.render.failed`, `release.mismatch.detected`,
|
||||
`telemetry.delivery.dropped`를 production path에 연결한다. cache와 storage
|
||||
실패는 diagnostics로 기록하되 raw key/value를 기록하지 않는다.
|
||||
7. queue full, invalid event/context, serialization과 sink failure는 제한된
|
||||
reason bucket으로 집계한다. drop observer의 failure는 다시 telemetry를
|
||||
발행하지 않는 nonrecursive 경계다.
|
||||
8. diagnostics와 telemetry failure는 제품 흐름, HTTP 결과, route transition,
|
||||
storage/cache fallback과 React error surface를 바꾸지 않는다.
|
||||
9. mount 전 bootstrap failure는 안전한 build/config/error kind만 별도 evidence로
|
||||
만들며 untrusted error message, stack과 support 입력을 serialize하지 않는다.
|
||||
10. 실제 error reporter, RUM, analytics나 tracing SDK는 같은 port 뒤의 외부
|
||||
adapter로만 추가한다. SDK type과 event API를 application/feature/presentation
|
||||
public contract에 노출하지 않는다.
|
||||
|
||||
## 실행 경계
|
||||
|
||||
```text
|
||||
route/application/HTTP/cache/storage/bootstrap
|
||||
-> typed DiagnosticsPort 또는 TelemetryPort
|
||||
-> registry + allowlist + value policy
|
||||
-> bounded memory/no-op 또는 best-effort HTTP adapter
|
||||
-> 프로젝트가 선택한 외부 sink
|
||||
```
|
||||
|
||||
- diagnostics contract: `src/contracts/diagnostics.ts`
|
||||
- telemetry contract: `src/contracts/telemetry.js`
|
||||
- application ports: `src/application/ports/diagnostics-port.ts`,
|
||||
`telemetry-port.ts`
|
||||
- bounded diagnostics: `src/adapters/diagnostics/bounded-diagnostics.ts`
|
||||
- best-effort telemetry: `src/adapters/telemetry/best-effort-telemetry.js`
|
||||
- composition: `src/bootstrap/runtime-adapters.js`
|
||||
|
||||
## 검증
|
||||
|
||||
- `check:diagnostics`는 모든 registry event에 production producer가 있는지와
|
||||
source의 direct console/sensitive context 우회를 검사한다.
|
||||
- negative source fixture는 direct console, unknown event와 raw context를 실제로
|
||||
거절하며 TypeScript fixture는 잘못된 level/event ID를 거절한다.
|
||||
- unit test는 allowlist, hostile/circular error, bounded diagnostics, no-op,
|
||||
queue full, sink/observer failure와 pre-mount boot evidence를 검증한다.
|
||||
- HTTP integration은 success, retry recovery, terminal failure와 abort의 producer
|
||||
횟수, route/operation/correlation context와 요청 값 비노출을 검증한다.
|
||||
- cache/storage/release/application/runtime test는 각 production wiring과
|
||||
diagnostics failure isolation을 검증한다.
|
||||
|
||||
## 한계와 재검토 조건
|
||||
|
||||
기본 adapter는 운영 log 검색, source map 연계, session replay, distributed span,
|
||||
analytics consent, sampling budget과 장기 보존을 제공하지 않는다. 실제 sink를
|
||||
선정할 때 데이터 처리 지역, 보존 기간, consent, CSP, source map 접근 제어,
|
||||
sampling과 비용 상한을 별도 결정해야 한다.
|
||||
|
||||
## Rollback
|
||||
|
||||
telemetry exporter는 runtime 설정을 끄거나 adapter wiring을 `noOpTelemetry`로
|
||||
바꾸어 독립적으로 제거할 수 있다. 이때도 `DiagnosticsPort`, registry,
|
||||
redaction/value policy, producer-count와 negative fixture는 유지한다. 외부 SDK
|
||||
문제로 application producer와 안전 계약을 함께 되돌리지 않는다.
|
||||
@@ -33,14 +33,14 @@
|
||||
|
||||
특히 다음은 선행 해결이 필요하다.
|
||||
|
||||
RP-01~RP-08에서 TypeScript 도구 안전망, application runtime 주입,
|
||||
RP-01~RP-09에서 TypeScript 도구 안전망, application runtime 주입,
|
||||
query/mutation inbound adapter, HTTP 실행 계약과 executable route/release
|
||||
recovery 계약, 제거 가능한 reference 수직 슬라이스, form/page, design system과
|
||||
i18n 실행 경계는 구현됐다. 현재 선행 해결
|
||||
i18n 실행 경계와 diagnostics/telemetry production wiring은 구현됐다. 현재 선행 해결
|
||||
대상은 다음과 같다.
|
||||
|
||||
1. diagnostics/telemetry 실제 producer 연결
|
||||
2. registry evidence, 공급망과 optional adapter recipe 심화 게이트
|
||||
1. registry evidence와 실제 compatibility diff
|
||||
2. 공급망과 optional adapter recipe 심화 게이트
|
||||
|
||||
따라서 현재 상태를 “프론트 공통부가 모두 구현됐다”고 표현하면 범위가 과장된다.
|
||||
더 정확한 표현은 다음과 같다.
|
||||
@@ -66,7 +66,7 @@ i18n 실행 경계는 구현됐다. 현재 선행 해결
|
||||
| 계층 의존 방향 | 부분 준비 | `.dependency-cruiser.cjs`, `src/application/ports` | inbound/outbound 명명과 `contracts` 소유권까지 집행 |
|
||||
| application facade | 준비됨 | typed input/output catalog, provider, production composition test | feature input use case를 contribution으로 확장 |
|
||||
| HTTP client | 준비됨 | path/search/body projection, runtime timeout/retry, abort/cleanup test | feature gateway 뒤에서 사용 |
|
||||
| retry | 준비됨 | HTTP 단일 소유, runtime max attempts, Query retry off | RP-09에서 telemetry 연결 |
|
||||
| retry | 준비됨 | HTTP 단일 소유, runtime max attempts, Query retry off, logical execution당 bounded diagnostics | terminal event 중복 방지 계약 유지 |
|
||||
| 오류 모델 | 부분 준비 | error registry와 normalization 존재 | typed discriminated union과 계층별 mapper |
|
||||
| 검증 | 준비됨 | runtime/API/route/form Zod parse 결과를 실행 경계에서 사용하고 domain invariant와 분리 | feature별 schema 소유권 유지 |
|
||||
| 인증 연동 | 준비됨/프로젝트 선택 | opaque auth owner와 demo seam 존재 | 인증 방식별 recipe; 기본 token 저장소는 추가하지 않음 |
|
||||
@@ -81,8 +81,8 @@ i18n 실행 경계는 구현됐다. 현재 선행 해결
|
||||
| 아이콘 | 준비됨 | Lucide static vendor facade와 semantic icon/IconButton 접근성 계약 | 의미 icon 추가 시 bundle/접근성 기준 적용 |
|
||||
| 폼 | 준비됨 | Zod 기반 local facade, error summary/focus, 422 allowlist, dirty/pending/conflict 정책 | 복합 form 요구가 생기면 VD-04 조건으로 vendor adapter 평가 |
|
||||
| 국제화 | 준비됨 | 137-key typed catalog, locale provider, Intl formatter, safe fallback/alias, pseudo·RTL gate | 실제 locale·번역 승인은 프로젝트에서 연결 |
|
||||
| logging/diagnostics | 미제공 | telemetry port는 있으나 logger 없음 | redaction이 적용된 diagnostics/logging 경계 |
|
||||
| telemetry | 부분 준비 | registry, queue, redaction 존재 | HTTP·boot·cache·storage·route 사건에 실제 연결 |
|
||||
| logging/diagnostics | 준비됨 | 별도 `DiagnosticsPort`, 8-event registry, allowlist, bounded/no-op adapter와 production producer | 실제 프로젝트의 remote sink는 port 뒤에서 선택 |
|
||||
| telemetry | 준비됨/프로젝트 선택 | 5-event registry, bounded queue, redaction/value policy, boot·HTTP·render·release·drop producer | analytics/RUM/error vendor와 consent는 프로젝트에서 선택 |
|
||||
| 비동기 상태 불변식 | 준비됨 | 배타적 typed overlay, stale latch, 실제 retry/conflict action | reference 화면에서 전체 상태 전시 |
|
||||
| 단위·통합·E2E | 준비됨 | Vitest, RTL, MSW, Playwright 3엔진 | TS 테스트 검사, 실제 bootstrap 통합, 위험 시나리오 보강 |
|
||||
| UI 회귀 검증 | 미제공 | axe/reflow는 있으나 visual baseline 없음 | Storybook 또는 동급 workshop과 시각 회귀 |
|
||||
@@ -217,11 +217,14 @@ guard → browser navigation adapter의 1회 reload로 연결됐다. 일반 rend
|
||||
failure는 이 경로에서 제외되고, 반복 실패·offline·malformed manifest·storage
|
||||
실패는 지원 표면으로 fail-closed된다.
|
||||
|
||||
#### telemetry, registry, 공급망 gate의 실행 깊이가 부족하다
|
||||
#### RP-09에서 diagnostics/telemetry 실행 깊이 보강
|
||||
|
||||
telemetry registry에는 여러 사건이 있지만 실제 production producer는 제한적이다.
|
||||
boot, API attempt/final failure, auth recovery, storage/cache degradation, release/chunk
|
||||
recovery를 registry 사건에 연결해야 한다.
|
||||
`DiagnosticsPort`와 `TelemetryPort`를 분리하고 boot, HTTP logical outcome,
|
||||
render, cache, storage, route, release mismatch와 delivery drop을 production
|
||||
producer에 연결했다. HTTP retry는 attempt별 terminal event를 발행하지 않고
|
||||
logical execution 종료 시 한 번만 bounded outcome을 남긴다. allowlist와
|
||||
value policy가 raw URL/query/body/storage value/error object를 거절하고 queue와
|
||||
sink failure는 nonrecursive drop evidence로 제한된다.
|
||||
|
||||
registry/compatibility 검사는 다음까지 확장한다.
|
||||
|
||||
@@ -243,7 +246,7 @@ known vulnerability, license policy, SBOM/provenance를 pinned tool로 검사해
|
||||
- token → primitive → pattern → template로 이어지는 디자인 시스템
|
||||
- Lucide를 감싼 local icon registry와 `IconButton`
|
||||
- typed message key, locale provider, formatter, pseudo-locale/RTL smoke
|
||||
- redacted structured diagnostics/logger와 telemetry wiring
|
||||
- RP-09에서 완료한 redacted structured diagnostics와 telemetry wiring 유지
|
||||
- Storybook 또는 동급 isolated UI workshop
|
||||
- Playwright visual baseline, shared MSW scenarios, built-dist E2E
|
||||
- React Hooks, JSX accessibility, TanStack Query 관련 lint
|
||||
@@ -380,7 +383,8 @@ bootstrap → React TSX → tests 순서로 이동한다.
|
||||
|
||||
- retry: `src/adapters/http/retry-policy.js`, 부분 준비
|
||||
- API client: `src/adapters/http/client.js`, 부분 준비
|
||||
- logger: 없음. telemetry와 분리하거나 diagnostics port로 합치는 결정 필요
|
||||
- logger: `DiagnosticsPort`로 telemetry와 분리해 구현. closed event/level,
|
||||
allowlist와 bounded/no-op adapter 제공
|
||||
- token manager: 의도적으로 없음. opaque external auth owner가 credential을 소유
|
||||
- error: `src/contracts/errors.js`와 HTTP normalization, 부분 준비
|
||||
- validation: runtime/API Zod는 존재, route/form/domain 분리는 미완성
|
||||
|
||||
@@ -723,6 +723,28 @@ render와 release 사건을 실제 producer에 연결한다.
|
||||
RP-09는 exporter를 제거하고 즉시 no-op adapter로 전환할 수 있어야 한다.
|
||||
diagnostics port와 redaction test는 유지한다.
|
||||
|
||||
**구현 증거 (2026-07-26)**
|
||||
|
||||
- VD-07에서 level/event 기반 `DiagnosticsPort`와 semantic `TelemetryPort`를
|
||||
분리하고 bounded memory/no-op 또는 설정 기반 best-effort HTTP exporter를
|
||||
채택했다.
|
||||
- diagnostics 8종과 telemetry 5종의 registry, context/attribute allowlist,
|
||||
고카디널리티 value policy, timestamp와 status/attempt/duration/queue bucket을
|
||||
구현했다.
|
||||
- boot, route, render, HTTP logical outcome, cache, storage, release mismatch와
|
||||
telemetry drop을 production composition에 연결했다. HTTP는 success/recovery/
|
||||
terminal/abort 각각 logical execution당 diagnostics 한 번, terminal
|
||||
non-abort failure telemetry 한 번만 발행한다.
|
||||
- bounded oldest-drop queue, drop reason 집계, sink/observer failure 격리와
|
||||
nonrecursive delivery evidence를 구현했다. exporter가 없으면 network와 queue
|
||||
side effect가 없는 true no-op이다.
|
||||
- `check:diagnostics`, source negative fixture와 TypeScript negative fixture가
|
||||
producer 누락, direct console, unknown event/context, raw URL/query/body/
|
||||
credential 경계를 거절한다.
|
||||
- unit/integration test가 circular/hostile error, pre-mount boot, queue/sink
|
||||
failure, no-op, cache/storage/release producer, success/retry recovery/terminal/
|
||||
abort 횟수와 reference route/operation/correlation context를 검증한다.
|
||||
|
||||
### 10. `feature-frontend-test-registry-evidence-hardening`
|
||||
|
||||
**목표**
|
||||
|
||||
@@ -779,22 +779,31 @@ Logger와 telemetry는 같은 것이 아니다.
|
||||
- Telemetry: registry에 정의된 semantic event와 metric
|
||||
- Error reporter: 예외 집계와 release correlation
|
||||
|
||||
기본 `Logger` output port는 safe context만 받는다.
|
||||
VD-07에 따라 기본 구현은 임의 message 문자열을 받는 `Logger`가 아니라 닫힌
|
||||
event ID와 safe context만 받는 `DiagnosticsPort`다.
|
||||
|
||||
```ts
|
||||
export interface Logger {
|
||||
debug(message: string, context?: SafeLogContext): void;
|
||||
info(message: string, context?: SafeLogContext): void;
|
||||
warn(message: string, context?: SafeLogContext): void;
|
||||
error(message: string, context?: SafeLogContext): void;
|
||||
export interface DiagnosticsPort {
|
||||
record(input: {
|
||||
level: DiagnosticLevel;
|
||||
eventId: DiagnosticEventId;
|
||||
context?: DiagnosticContext;
|
||||
}): void;
|
||||
}
|
||||
```
|
||||
|
||||
개발 환경에는 redacted console adapter, production에는 allowlist 기반
|
||||
remote adapter, 테스트에는 recording 또는 no-op adapter를 연결한다.
|
||||
기본 runtime에는 bounded in-memory diagnostics와 설정 기반 best-effort
|
||||
telemetry adapter, 테스트에는 recording 또는 no-op adapter를 연결한다.
|
||||
direct `console`은 redaction 경계를 우회하므로 source gate가 거절한다.
|
||||
민감정보 redaction은 각 호출자의 선의가 아니라 adapter와 contract에서
|
||||
강제한다.
|
||||
|
||||
현재 production producer는 boot, logical HTTP outcome, cache, storage, route,
|
||||
render, release mismatch와 telemetry delivery drop을 포함한다. HTTP terminal
|
||||
telemetry는 모든 retry가 끝난 뒤 한 번만 발행하며 abort에는 발행하지 않는다.
|
||||
raw path/query/body/error 대신 route/operation/correlation ID와
|
||||
status/attempt/duration bucket만 전달한다.
|
||||
|
||||
## 16. Feature 경계와 removable reference feature
|
||||
|
||||
Reference feature는 단순 UI fixture가 아니라 다음 경로를 모두 실행해야
|
||||
@@ -1223,9 +1232,10 @@ contract와 실패 분기를 우선한다.
|
||||
- [ ] runtime timeout/retry 설정이 실제 HTTP transport에 반영된다.
|
||||
- [ ] QueryClient와 feature query bridge가 실제 route에서 동작한다.
|
||||
- [ ] session UI API와 credential attachment가 분리되어 있다.
|
||||
- [ ] logger와 telemetry가 HTTP/render/storage/cache failure 경로에
|
||||
- [x] diagnostics와 telemetry가 HTTP/render/storage/cache failure 경로에
|
||||
연결된다.
|
||||
- [ ] page lifecycle에서 필요한 telemetry flush/cleanup이 수행된다.
|
||||
- [x] `pagehide`에서 bounded telemetry queue를 flush하고 adapter `dispose`가
|
||||
lifecycle listener를 정리한다.
|
||||
|
||||
### 26.3 HTTP와 validation
|
||||
|
||||
|
||||
@@ -468,23 +468,31 @@ raw response body, stack, token, URL query, PII를 사용자 copy나 일반 log
|
||||
|
||||
### 7.3 diagnostics와 telemetry
|
||||
|
||||
현재 telemetry event contract와 별도로 개발·진단용 structured logger가 필요하다.
|
||||
다음 두 설계 중 하나를 ADR로 결정한다.
|
||||
|
||||
1. `DiagnosticsPort`가 log/event/span을 내부 method로 구분
|
||||
2. `LoggerPort`와 `TelemetryPort`를 분리
|
||||
VD-07에서 level/event 기반 `DiagnosticsPort`와 semantic
|
||||
`TelemetryPort`를 분리했다. diagnostics는 8개 event ID와 safe context
|
||||
allowlist를, telemetry는 event별 required/optional attribute와 value policy를
|
||||
사용한다.
|
||||
|
||||
공통 요구:
|
||||
|
||||
- log level과 event key는 닫힌 union
|
||||
- attribute allowlist와 중앙 redaction
|
||||
- dev adapter는 console을 사용하되 동일 redaction 적용
|
||||
- production adapter는 provider SDK를 감싸며 앱 코드는 SDK를 import하지 않음
|
||||
- 기본 adapter는 bounded memory/no-op이고 endpoint가 있을 때만 best-effort
|
||||
HTTP queue를 사용
|
||||
- production provider SDK를 추가할 때도 port 뒤에서 감싸며 앱 코드는 SDK를
|
||||
import하지 않음
|
||||
- 오류 객체 전체를 그대로 serialize하지 않음
|
||||
- trace ID/build ID/route ID/operation ID를 허용된 범위에서 연결
|
||||
- logging failure가 제품 flow를 실패시키지 않음
|
||||
- consent가 필요한 analytics와 essential diagnostics를 분리
|
||||
|
||||
HTTP는 route/operation/correlation ID를 logical execution context로 생성하고
|
||||
success/recovered/failed/aborted 종료 시 diagnostics를 한 번만 기록한다.
|
||||
terminal non-abort failure만 telemetry를 한 번 발행한다. cache/storage와 boot
|
||||
producer는 raw key, value, message, stack을 버리고 error kind와 bounded
|
||||
operation만 남긴다. queue full과 sink failure는 제한된 reason bucket이며 drop
|
||||
observer가 실패해도 재귀 발행하지 않는다.
|
||||
|
||||
### 7.4 locale, message와 표시 값
|
||||
|
||||
RP-08부터 locale은 presentation-owned React context다. server/application
|
||||
|
||||
@@ -410,6 +410,8 @@ Page / AsyncSurface / Form pattern
|
||||
- filter 순서가 달라도 canonical key가 같다.
|
||||
- route unmount 또는 superseded input에서 request를 abort한다.
|
||||
- aborted request는 terminal error나 telemetry failure로 오분류되지 않는다.
|
||||
- success와 retry recovery는 terminal failure telemetry를 만들지 않고,
|
||||
exhausted retry는 logical execution당 한 번만 발행한다.
|
||||
- offline/paused와 loading을 구분한다.
|
||||
- 401 복구는 한 번만 수행한다.
|
||||
- 재로그인 후 허용된 operation만 다시 실행한다.
|
||||
@@ -1283,7 +1285,8 @@ CI registry에 추가한다.
|
||||
- [ ] high-risk module branch 목표를 충족한다.
|
||||
- [ ] 신규 코드의 미검증 branch에 승인 없는 예외가 없다.
|
||||
- [ ] bundle/performance budget을 통과한다.
|
||||
- [ ] telemetry/error output에 민감 정보가 없다.
|
||||
- [x] diagnostics/telemetry output의 allowlist, value policy와 negative fixture가
|
||||
raw URL/query/body/storage value/error object를 거절한다.
|
||||
- [ ] 관련 gate와 evidence registry가 갱신되었다.
|
||||
|
||||
## 19. 금지 패턴
|
||||
|
||||
Reference in New Issue
Block a user