Skip to content

관측성 로깅

한국어 번역본

이 문서는 원문(영어)의 한국어 번역이다. 정본은 원문이며, 계약·게이트 판단이 갈리면 원문을 따른다. 원문: docs/architecture/observability-logging.md

갱신: 2026-07-30

Barbelic은 느린 데이터와 인입(import) 진단을 위해 가벼운 운영 로그를 유지한다. 로그는 원본 운동 페이로드, 업로드된 파일 내용, OAuth 토큰, 공급자 원본 페이로드를 노출하지 않으면서 어디에 시간이 쓰였는지 설명해야 한다.

상관 필드

다음 필드들을 일관되게 사용한다:

  • operationId / operation_id: 생성된 단일 오퍼레이션 식별자.
  • operationType: rpcwodup_import 같은 넓은 범주.
  • operationName: get_home_dashboardwodup-start-import 같은 구체적인 오퍼레이션.
  • durationMs / duration_ms: 경과 시간(밀리초).
  • responseBytes: 프런트엔드에서의 대략적인 JSON 응답 크기.
  • errorCode / error_code: 가능한 경우의 안정적인 에러 코드.
  • batchId / batch_id: Wodup 인입 배치 id.
  • job_id: 통계 갱신 잡 id.

영속 애플리케이션 에러 이벤트

인증된 브라우저, React 렌더, 반환된 서버/RPC, 네트워크, 로컬 초안 영속화 실패는 record_client_error_event(jsonb)를 통해 비공개 client_error_events 테이블로 전송된다.

애플리케이션은 다음 캡처 경계를 설치한다:

  • 브라우저 errorunhandledrejection 이벤트;
  • 실패한 비-RPC fetch 응답과 네트워크 거부(rejection) (RPC 실패는 리포지토리 경계에서 한 번만 기록된다 — 읽기는 callScreenRpc, 쓰기는 callMutationRpc가 각각 RPC 실명으로 적재한다);
  • React 에러 바운더리;
  • 화면 RPC 및 엄격(strict) 응답 어댑터 실패;
  • 쓰기 RPC 실패(callMutationRpc, #664 Phase 2) 및 초안 영속화 실패;
  • 모든 사용자 노출 에러 문구는 reportedUserError 관문을 지난다(#664 Phase 1): 문구는 미보고 에러를 적재한 뒤에만 반환되고, PUBLIC_ERROR_MESSAGES 등재 코드는 예상된 사용자 오류로 보고에서 제외되며, RPC 도달 전 클라 불변식 위반은 clientContractErrorclient_contract_error로 분류한다.

보고-1회 마커(오너 수명, 래핑 에러의 cause 체인 포함)가 그물이 겹쳐도 에러 객체당 1건 적재를 보장한다.

브라우저는 sessionStorage["barbelic-pending-error-events-v1"]에 미전송 이벤트를 최대 50개까지 보관하며, 인증된 세션이나 복구된 연결을 확보한 뒤 재시도한다. 일시적 실패 뒤에는 상한이 있는 지수 백오프를 적용한다. 대기 중 항목은 인증된 사용자별로 분할된다. 계정을 전환해도 이전 계정의 항목이 새 auth.uid()로 제출될 수 없다. 유효하지 않거나 영구적으로 거부된 클라이언트 봉투(envelope)는 전송 상태에서 폐기되어 이후 진단을 막지 못한다. 일반적인 중복 이벤트는 30초 동안 억제되고, 브라우저 인제스트는 분당 20개로 제한된다. 데이터베이스는 별개로 인증된 인제스트를 분당 60개, 24시간당 1,000개로 제한한다.

서버는 모든 행을 auth.uid()에 묶고 전송 계층의 expected_user_id를 검증한다. 일반 사용자는 이 테이블을 직접 읽거나 삽입·수정·삭제할 수 없으며, service-role 진단은 읽을 수 있다. 익명 인제스트는 의도적으로 비활성화되어 있다. 같은 클라이언트 이벤트 ID를 다른 내용으로 재전송하는 것은 서버가 계산한 페이로드 해시로 거부된다.

자유 형식 예외 메시지와 URL/리소스 경로는 서버 인제스트 계약에 결코 포함되지 않는다. 저장되는 행에는 제한된 분류값, 에러 코드, 계약 필드 경로, 오퍼레이션 키, 수치형 상태/타이밍 데이터, 그리고 에셋 basename/행/열 스택 프레임만 담긴다. 토큰, 인가 데이터, 이메일, 제목, 파일 이름, 쿼리 문자열, 요청/응답 본문, 운동 페이로드, 공급자 식별자, 원본 user-agent 데이터는 제외된다.

사용자에게 보이는 에러 표면에는 애플리케이션이 소유한 문구만 렌더링된다. 원본 공급자, RPC, 응답 계약, 인입 에러 문자열은 일반 및 관리자 워크플로 화면 밖에 둔다. 운영 세부 사항은 비공개 서버 로그에서 확인한다.

개발 기간에는 TTL, purge RPC, cron 잡, 자동 삭제 정책을 설치하지 않는다. 저장된 행은 명시적인 수동 보존 결정이 내려질 때까지 남는다. 상한이 있는 브라우저 대기 큐는 전송 상태이지 서버 로그가 아니다.

프런트엔드 디버그 링

화면 RPC 호출은 호환용 RPC 로그와 일반 오퍼레이션 로그를 모두 기록한다.

메모리 내 링은 동기적으로 갱신된다. sessionStorage는 링당 한 번만 하이드레이트되고, 링 전체의 영속화는 idle 콜백(또는 타이머 폴백)으로 합쳐진다. 그래서 RPC 완료 시점에 화면이 렌더될 수 있기 전에 두 히스토리 버퍼를 반복해서 파싱하고 문자열화하지 않는다.

저장 위치:

  • globalThis.__liftGuildRpcDebug
  • sessionStorage["barbelic-rpc-debug-log"]
  • globalThis.__liftGuildOperationDebug
  • sessionStorage["barbelic-operation-debug-log"]

콘솔 출력은 옵트인이다:

  • ?debug-rpc=1 또는 ?lg-rpc-debug=1
  • ?debug-ops=1 또는 ?lg-operation-debug=1
  • localStorage["barbelic-rpc-debug"] = "1"
  • localStorage["barbelic-operation-debug"] = "1"

필수 화면 RPC 필드:

  • event: screen_rpc:started, screen_rpc:completed, 또는 screen_rpc:failed
  • operationId
  • operationType: "rpc"
  • operationName
  • rpcName
  • durationMs
  • responseBytes
  • paramKeys
  • 실패 시 errorCode와 안전한 error 객체

Wodup 인입 프런트엔드 이벤트:

  • wodup_import_upload:started|completed|failed
  • wodup_import_start:started|completed|failed
  • wodup_import_poll:completed|failed

이 이벤트들은 batchId, 파일 이름, 파일 크기, 인입 상태, 개수를 포함할 수 있다. 원본 JSONL 텍스트나 원본 공급자 페이로드는 포함해서는 안 된다.

완료-운동 통계 디스패치 이벤트:

  • stats_refresh_dispatch:started
  • stats_refresh_dispatch:coalesced
  • stats_refresh_dispatch:completed
  • stats_refresh_dispatch:failed

이 이벤트들은 소스 오퍼레이션과 세션 id를 포함하지만, 운동 페이로드는 결코 포함하지 않는다. coalesced는 같은 사용자의 다른 쓰기가 병렬 처리기를 새로 시작하는 대신 활성화된 상한 드레인(bounded drain)에 합류했다는 뜻이다. 실패한 디스패치는 경고로 기록되는데, 정본 쓰기는 이미 커밋되었고 데이터베이스 cron 잡이 복구 경로로 남아 있기 때문이다.

Edge Function 로그

Vercel 인증 핸들러는 설정, 공급자 교환(exchange), 예기치 못한 요청 실패에 대해 구조화된 [barbelic server error] 항목을 남긴다. 이 항목에는 이벤트 키, 타임스탬프, 에러 이름/코드, 안전한 수치형 메타데이터만 담긴다. 임의의 예외 메시지, 공급자 응답 텍스트, 자격 증명은 제외된다. 이들의 HTTP/네이티브 에러 응답은 애플리케이션이 소유한 문구만 사용하므로, 공급자 진단 정보가 에러 페이지가 되지 않는다.

wodup-start-import, wodup-process-import-jobs, stats-process-refresh-jobs는 다음을 담은 구조화된 서버 로그를 기록한다:

  • operation_id
  • batch_id
  • user_id
  • status
  • 종료(terminal) 이벤트의 duration_ms
  • 실패 시 error_code
  • session_count, session_exercise_count, set_count 같은 단계별 카운트

프런트엔드는 요청 본문과 x-lift-guild-operation-id 헤더로 Edge Function에 operation_id를 보낸다. 그래서 실패한 start 호출을 브라우저 로그와 Supabase 함수 로그 사이에서 대조할 수 있다.

Wodup 인입 서버 이벤트:

  • wodup_import:received
  • wodup_import:batch_loaded
  • wodup_import:queued
  • wodup_import:worker_dispatched
  • wodup_import:worker_dispatch_failed
  • wodup_import:worker_received
  • wodup_import:claimed
  • wodup_import:normalized
  • wodup_import:staged
  • wodup_import:importing
  • wodup_import:canonical_completed
  • wodup_import:completed
  • wodup_import:worker_completed
  • wodup_import:worker_job_failed
  • wodup_import:failed

start 요청은 보통 wodup_import:queuedwodup_import:worker_dispatched 부근에서 끝나야 한다. 더 긴 정규화와 정본 인입 작업은 wodup-process-import-jobs에서 실행되며, 그동안 UI는 wodup_import_batches를 폴링한다.

통계 갱신 워커 이벤트:

  • stats_refresh:worker_received
  • stats_refresh:worker_completed
  • stats_refresh:worker_failed

DB 통계 잡 로그

user_exercise_stats_refresh_jobs는 여전히 쓰기 측 생명주기 테이블이다. 운영 목적의 조회에는 다음을 사용한다:

sql
select *
from public.user_exercise_stats_refresh_job_observability
order by updated_at desc
limit 50;

이 뷰가 노출하는 것:

  • job_id
  • user_id
  • event_type
  • source_ref
  • status
  • operation_id
  • duration_ms
  • error_code
  • error_message
  • processing_started_at
  • finished_at

이 뷰는 일반 인증 앱 사용자에게 부여되지 않는다. 서비스/관리자 진단을 위한 것이다.