로깅은 엉망이다

1 month ago 12

  • 현대 분산 시스템에서 기존 로그 방식은 진실을 전달하지 못하는 구조적 한계를 지님
  • 로그는 여전히 2005년식 단일 서버 환경을 전제로 설계되어, 다중 서비스·데이터베이스·캐시를 거치는 요청의 컨텍스트를 잃어버림
  • 단순 문자열 검색은 구조·관계·상관성을 이해하지 못해, 문제 원인을 찾기 어렵게 만듦
  • 해결책은 각 요청마다 모든 맥락을 담은 단일 ‘Wide Event(또는 Canonical Log Line)’ 을 남기는 것
  • 이를 통해 로그가 단순 텍스트가 아닌 분석 가능한 데이터 자산으로 전환됨

로깅의 근본적 문제

  • 기존 로그는 모놀리식 서버 시대를 전제로 만들어져, 현대의 분산 서비스 구조를 반영하지 못함
    • 한 요청이 여러 서비스·DB·캐시·큐를 거치지만, 로그는 여전히 단일 서버 기준으로 기록됨
  • 예시 로그에서는 한 요청당 13줄이 생성되어, 10,000명 동시 사용자 시 초당 130,000줄이 쏟아지지만 대부분 무의미한 정보임
  • 문제 발생 시 필요한 것은 맥락(context) 이지만, 현재 로그에는 그것이 결여되어 있음

문자열 검색의 한계

  • 사용자가 “결제가 안 된다”고 보고했을 때, 이메일이나 user_id로 로그를 검색해도 일관된 구조가 없어 유효한 결과를 얻기 어려움
    • 동일한 사용자 ID가 user-123, user_id=user-123, {"userId":"user-123"} 등 수십 가지 형태로 기록됨
  • 서비스 간 로그 포맷이 달라 연관 이벤트 추적이 불가능
  • 핵심 문제는 로그가 쓰기(write) 중심으로 설계되어 있고, 조회(query) 에는 최적화되어 있지 않다는 점

핵심 개념 정의

  • Structured Logging: 문자열 대신 키-값(JSON) 형태로 기록하는 방식
  • Cardinality(카디널리티) : 필드의 고유값 개수, 예를 들어 user_id는 매우 높음
  • Dimensionality(차원 수) : 로그 이벤트 내 필드 개수, 많을수록 분석 가능성이 높음
  • Wide Event / Canonical Log Line: 요청당 하나의 맥락이 풍부한 단일 로그 이벤트
  • 대부분의 로깅 시스템은 고카디널리티 데이터를 비용 문제로 제한하지만, 실제로는 그것이 디버깅에 가장 유용함

OpenTelemetry의 한계

  • OpenTelemetry(OTel)는 프로토콜과 SDK 세트로, 데이터 수집·전송 표준만 제공
  • 그러나 OTel은 다음을 수행하지 않음
    1. 무엇을 로깅할지 결정하지 않음
    2. 비즈니스 맥락(예: 구독 등급, 장바구니 금액 등)을 자동으로 추가하지 않음
    3. 개발자의 로깅 사고방식을 바꾸지 않음
  • 동일한 라이브러리를 사용해도, 맥락을 의도적으로 추가한 계측과 단순 계측의 디버깅 경험은 극명히 다름
  • OTel은 단순한 전달 수단(plumbing) 이며, 무엇을 흘려보낼지는 개발자가 결정해야 함

Wide Event / Canonical Log Line 방식

  • 기존의 “코드가 무엇을 하는가” 중심 로깅에서 벗어나, “요청에 무슨 일이 일어났는가”를 기록해야 함
  • 각 요청마다 서비스 단위로 하나의 광범위한 이벤트를 생성
    • 요청·사용자·결제·오류·환경 등 50개 이상의 필드를 포함 가능
  • 예시 JSON에는 user_id, subscription_tier, service_version, error_code 등 모든 디버깅 맥락이 포함
  • 이를 통해 단일 검색으로 “프리미엄 사용자의 결제 실패 원인” 등 즉시 분석 가능

Wide Event의 쿼리 활용

  • Wide Event는 단순 텍스트 검색이 아닌 구조화된 데이터 쿼리로 다룸
  • 고카디널리티·고차원 데이터 기반으로 실시간 분석 수준의 디버깅 가능
  • 예: “지난 1시간 동안 프리미엄 사용자의 결제 실패율을 오류 코드별로 집계” 같은 쿼리를 즉시 실행 가능

구현 패턴

  • 요청 수명주기 전체에서 이벤트를 구성하고, 마지막에 한 번만 출력
    • 미들웨어에서 request_id, timestamp, method, path 등 기본 필드 초기화
    • 핸들러에서 사용자·장바구니·결제·오류 정보를 점진적으로 추가
  • 최종적으로 logger.info(event)로 단일 JSON 이벤트를 기록

샘플링으로 비용 제어

  • 요청당 50필드 이상을 기록하면 비용이 급증하므로 샘플링 필요
  • 단순 무작위 샘플링은 오류를 놓칠 위험이 있음
  • Tail Sampling 전략 제시
    1. 오류(500 등)는 항상 저장
    2. 느린 요청(p99 이상)은 항상 저장
    3. VIP 사용자·특정 플래그 세션은 항상 저장
    4. 나머지는 1~5%만 무작위 샘플링
  • 이를 통해 비용 절감과 핵심 이벤트 보존을 동시에 달성

흔한 오해 정리

  • Structured Logging ≠ Wide Event: JSON 포맷만으로는 충분하지 않음, 맥락이 핵심
  • OpenTelemetry 사용 ≠ 완전한 관측성 확보: 수집만 표준화할 뿐, 무엇을 기록할지는 개발자 몫
  • Tracing과 동일하지 않음: 트레이싱은 서비스 간 흐름, Wide Event는 서비스 내부 맥락 제공
  • 로그는 디버깅용, 메트릭은 대시보드용이라는 구분은 불필요함 — Wide Event는 두 용도 모두 충족
  • 고카디널리티 데이터는 비싸다는 인식은 구식임, ClickHouse·BigQuery 등 현대 DB는 이를 효율적으로 처리

Wide Event 도입의 효과

  • 디버깅이 탐사(archaeology) 에서 분석(analytics) 으로 전환
  • “사용자 결제 실패”를 찾기 위해 50개 서비스 로그를 grep 하던 방식에서,
    “프리미엄 사용자 결제 실패율을 오류 코드별로 조회”하는 단일 쿼리 기반 분석으로 변화
  • 결과적으로 로그가 거짓을 말하던 도구에서, 진실을 말하는 데이터 자산으로 전환됨

Read Entire Article