FE(web) 분산추적 합류 FE 설계 (design-fe-web) — FR-10

Background

근거 PRD: /Users/biuea/Desktop/dpdpdndn/프로젝트/스포츠앱/옵저버빌리티 스택 도입/PRD.md (검수 PASS) — FR-10. 근거 BE TDD: /Users/biuea/Desktop/dpdpdndn/프로젝트/스포츠앱/옵저버빌리티 스택 도입/TDD.md — OTLP/Collector·env 태그·traceparent 규약을 입력으로 소비.

FR-10은 web 요청도 가능한 범위(Next.js 서버 런타임 한정)에서 BE trace에 합류시키는 것이다. 이 과제는 UI 기능이 아니다 — Next.js 서버 런타임(Server Component·Route Handler)에 OpenTelemetry를 계측하고, BFF가 BE를 호출할 때 traceparent(W3C Trace Context)를 전파해 BE(Micrometer Tracing)가 같은 trace로 잇게 한다.

Overview

  • 무엇을: web/instrumentation.ts로 OTel을 등록하고, OTLP로 Collector에 span을 보낸다. BFF fetch(lib/server/be-client.ts)가 나가는 요청에 traceparent를 실어 BE와 trace를 합류시킨다.
  • : 현재 web trace가 전무해, 사용자 요청이 web BFF → BE를 거칠 때 어느 구간이 병목인지 하나의 trace로 볼 수 없다. FR-10은 web ↔ BE 경계를 잇는 최소 조각이다.
  • 어떻게: @vercel/otel(Next 1급 지원)로 서버 런타임 계측 + global fetch 자동 계측(traceparent 주입). Next 14.2는 experimental.instrumentationHook 활성이 필요. env 태그는 신규 키 없이 BE 규약(deployment.environment)과 동일 값 체계 사용.

UI·화면·테마 토큰: 해당 없음. FR-10은 서버 런타임 계측이라 렌더되는 화면·컴포넌트·색이 없다. 따라서 화면 목록·와이어프레임·상태 표·테마 토큰 매핑은 이 과제에 존재하지 않는다(private-tdd FE 섹션 중 UI 항목은 “해당 없음 + 사유”로 대체). 브라우저 RUM 풀 계측은 PRD Non-Goals.

Terminology

용어정의
OTelOpenTelemetry — 분산 추적 표준
traceparentW3C Trace Context 헤더. 00-{traceId}-{spanId}-{flags}. trace 합류의 매개
OTLPOpenTelemetry Protocol. HTTP 4318 / gRPC 4317로 Collector 전송
@vercel/otelNext.js 서버 런타임용 OTel 등록 헬퍼(registerOTel). fetch 자동 계측·span export
instrumentation.tsNext가 프로세스 부팅 시 1회 실행하는 계측 진입 파일
BFFweb 서버가 BE를 대신 호출하는 계층(lib/server/be-client.ts)
env 태그BE와 공유하는 환경 구분(deployment.environment = APP_ENV)

Define Problem

AS-IS (실제 코드 근거)

요소현재 상태근거
OTel 의존성부재. @vercel/otel·@opentelemetry/* 미설치web/package.json (node_modules 확인)
instrumentation.ts없음web/ 루트 스캔 결과 공집합
Next 설정experimental.turbo만. instrumentationHook 미설정 (14.2는 필요)web/next.config.mjs
BFF fetchbeClient()가 global fetch 사용, Authorization(httpOnly 쿠키)·timeout만 부착. traceparent 없음lib/server/be-client.ts
런타임Route Handler·Server Component = Node 런타임 기본app/api/health/route.ts
env 값web은 NEXT_PUBLIC_APP_NAME·BACKEND_URL만. APP_ENV 계열 미사용app/layout.tsx, lib/server/be-client.ts
BE 측Micrometer Tracing + OTLP(4317) 계측 예정, isObservationEnabled=true로 incoming trace 수용BE TDD application.yml·KafkaConsumerConfig.kt 변경분

문제점

  1. web 서버에 OTel 등록이 없어 web 구간 span이 생성되지 않는다.
  2. BFF fetch가 traceparent를 전파하지 않아, web에서 시작한 trace가 BE에서 새 trace로 끊긴다.
  3. instrumentationHook 미설정이라 instrumentation.ts를 두어도 로드되지 않는다.

TO-BE

  • instrumentation.ts에서 registerOTel로 서버 런타임 OTel 등록(service.name·deployment.environment·OTLP endpoint).
  • next.config.mjsexperimental.instrumentationHook: true.
  • BFF fetch가 나가는 요청에 traceparent 자동 주입 → BE가 동일 trace로 합류(연결율 검증).
  • OTLP endpoint 미설정 시 no-op(BE TDD의 “플래그 등가”와 정합) — 앱 정상 부팅.

Architecture Benchmarking

사례참고 패턴미참고
Next.js 공식 OpenTelemetry (nextjs.org/docs/app/guides/open-telemetry)@vercel/otel + instrumentation.ts 등록, fetch 자동 계측·전파 채택수동 SDK 조립은 코드량 과다 → 미채택
BE TDD ADR-001 (Micrometer Tracing + OTel Bridge)server↔server는 traceparent 헤더 전파만으로 합류. web도 동일 규약 채택Java Agent 방식은 web 무관
사람인 OTel 도입 (saramin.github.io)“trace 연결이 병목 식별의 핵심” — web↔BE 경계 합류의 정당성MSA 서비스 메시 부분 미참조(단일 web↔BE)

Possible Solutions

방안설명채택 / 미채택
A. @vercel/otel + instrumentation.tsNext 1급 헬퍼. fetch 자동 계측·traceparent 전파, OTLP export config채택 — 최소 코드, Next 권장, BE traceparent 규약과 즉시 정합
B. @opentelemetry/sdk-node 수동 조립SDK·exporter·instrumentation을 직접 구성미채택 — 조립·버전 관리 부담, 지금 규모에 과함(단순함 우선)
C. 브라우저 RUM(클라이언트 계측)브라우저에서 span 생성미채택 — PRD Non-Goals(서버 런타임 한정)
D. BFF fetch에 traceparent 수동 헤더 생성직접 traceId 만들어 헤더 부착미채택 — A가 fetch 자동 계측으로 정확·표준 전파. 수동은 컨텍스트 불일치 위험

Detail Design

시스템 역할 경계

단위종류역할노출/전파의존
instrumentation.ts (신규, web 루트)계측 진입registerOTel 1회 호출 — service.name·resource attr·OTLP endpointOTLP span → Collector :4318@vercel/otel, env
next.config.mjs (수정)빌드 설정experimental.instrumentationHook: true
lib/server/be-client.ts (검증/미세수정)BFF나가는 fetch에 traceparent 전파 (fetch 자동 계측 대상 확인)BE로 traceparent 헤더fetch 계측
lib/server/otel-resource.ts (신규, 선택)configdeployment.environment(env)·service.name resource attr 산출env

계측 config 계약 (BE TDD 규약 인용)

# web 환경변수
OTEL_EXPORTER_OTLP_ENDPOINT = http://otel-collector:4318   # 미설정 시 export no-op (앱 정상)
OTEL_SERVICE_NAME           = sports-web                    # BE는 sports-application — 구분
APP_ENV                     = local|dev|prod                # BE와 동일 값 체계 (신규 키 없음)
# resource attribute
deployment.environment = ${APP_ENV}   # BE TDD env 태그 규약과 동일 (FR-8)

BE 계약 정합: BE TDD “인터페이스·엔드포인트 계약”의 otel-collector:4318(HTTP) 수신·deployment.environment resource attr과 일치. web span은 service.name=sports-web로 BE(sports-application)와 구분되며, 같은 deployment.environment로 대시보드에서 env 필터된다.

traceparent 전파 흐름 (Sequence)

sequenceDiagram
    participant B as 브라우저
    participant W as Next 서버(BFF)
    participant C as OTel Collector
    participant E as BE(Spring)
    B->>W: 페이지/Route Handler 요청
    W->>W: OTel span 생성 (root)
    W->>E: beClient fetch (traceparent 주입)
    E->>E: Micrometer Tracing이 traceparent로 합류
    E-->>W: 응답 (동일 trace)
    W->>C: web span OTLP export
    E->>C: BE span OTLP export
    W-->>B: 응답

Component Diagram

flowchart LR
    subgraph Web["Next 서버 런타임"]
        Instr["instrumentation.ts"]
        BFF["be-client fetch"]
    end
    Collector["OTel Collector :4318"]
    BE["BE Spring (Micrometer Tracing)"]
    Instr -->|registerOTel| BFF
    BFF -->|"fetch + traceparent"| BE
    BFF -->|OTLP span| Collector
    BE -->|OTLP span| Collector

실패 경로·동시성·멱등

시나리오영향설계 대응감지
OTEL_EXPORTER_OTLP_ENDPOINT 미설정web span export 안 됨no-op — 앱·요청 정상(BE TDD 플래그 등가)로컬 로그 경고 없음(정상)
Collector 다운web span 유실export best-effort, 요청 처리 무영향(장애 격리)Collector self-scrape(BE TDD)
traceparent 미전파web↔BE trace 분리fetch 자동 계측 검증 테스트로 회귀 감지E2E 샘플 trace 연결 검사
Edge 런타임 Route@vercel/otel Node 전제대상 Route는 Node 런타임 유지(기본), Edge 미사용빌드/런타임 확인
  • 동시성/멱등: 계측은 앱 상태를 바꾸지 않는다(읽기 전용 관측). 락·멱등 키 불필요.

상태 전이

해당 없음 — 관측 계측, 상태 머신 없음.

Testing Plan (implementer TDD 입력)

레벨대상케이스
buildpackage.json/next.config@vercel/otel 설치·해석; experimental.instrumentationHook 존재; 빌드 성공
unitotel-resourceAPP_ENVdeployment.environment 매핑; 미설정 시 기본값(local)
unit(계측 검증)be-client fetch나가는 요청 헤더에 traceparent가 존재(활성 span 컨텍스트 하에서); OTLP 미설정 시에도 요청 성공
integrationinstrumentation 등록register() 호출 시 예외 없이 OTel 등록; endpoint 미설정 no-op
scenario(E2E/수동)합류율임의 10건 web 요청 → Tempo에서 web+BE span이 동일 trace로 조회(9/10↑, NFR 99%)

핵심 실패 경로: ① OTLP 미설정에서 앱이 정상 부팅·요청 처리되는가(장애 격리) ② traceparent가 실제 BE 요청에 실리는가(회귀 감지) ③ instrumentationHook 누락 시 계측이 로드되지 않음을 빌드/실행으로 확인.

Release Scenario — 무중단 (BE TDD와 정합)

  • 배포 순서: web 계측 코드 배포 → Collector 준비 후 OTEL_EXPORTER_OTLP_ENDPOINT 주입으로 활성. endpoint 미주입이면 export no-op이라 Collector 없이도 web 정상.
  • 플래그 등가: endpoint 환경변수 = ON/OFF 스위치. 문제 시 언셋 → 즉시 no-op(앱 무영향).
  • 롤백: 계측 자체 문제 시 @vercel/otel 의존성·instrumentation.ts revert(단일 PR). traceparent 전파만 문제면 endpoint 언셋.
  • 기존 BFF·화면 동작 무변경(순수 가산). 회귀 위험은 fetch 계측이 헤더에 traceparent를 더하는 것뿐 — BE는 이를 수용(isObservationEnabled)하므로 안전.

Open Questions

  • web service.namesports-web으로 확정 — BE/INFRA 합의 필요확정됨 (2026-07-03, senior-pm #5): service.name 규약 = BE sports-application / web sports-web. ⑤ TDD “인터페이스·엔드포인트 계약” 표와 INFRA-03(collector/prometheus config)에 반영됨. Grafana/Tempo 대시보드가 service 라벨로 두 서비스를 구분(INFRA-04). 두 서비스 모두 deployment.environment=${APP_ENV} 공유.
  • Route별 Node/Edge 런타임 점검 — 현재 전부 Node 기본이나, 향후 Edge Route 추가 시 @vercel/otel 미적용 구간 발생 가능(문서화).

Document History

날짜변경 내용
2026-07-03최초 작성 — FR-10 서버 런타임 OTel 계측, traceparent 전파, UI 없음 명시, BE OTLP/env 규약 소비
2026-07-03senior-pm #5 — service.name 규약(BE=sports-application/web=sports-web) 확정으로 Open Question 종결