Logs Metrics Traces로 장애 위치 찾기
Observability의 세 기둥인 Logs, Metrics, Traces가 각각 무엇을 보여주고, 장애 대응에서 어떻게 함께 쓰이는지 정리한다. ADOT와 Collector 기반 수집, 구조화 로그, Golden Signals, Trace 전파, 그리고 대표 실패 모드를 함께 다룬다.
Script Companion
오디오와 함께 스크립트 보기
- 01
서비스에 문제가 생겼을 때 필요한 능력은 단순히 로그를 많이 보는 것이 아니라, 어디서 문제가 났는지 좁혀 가는 Observability다. Logs만 있으면 개별 사건은 보이지만 전체 흐름이 흐릿하고, Metrics만 있으면 이상 징후는 보이지만 원인은 잘 보이지 않는다. 프론트엔드에서 Performance API, Web Vitals, Lighthouse 점수로 성능을 숫자로 추적하던 감각은 서버 Observability로 이어진다. Lighthouse의 총 차단 시간 TBT는 서버 Metrics의 P99 응답 시간과 대응되고, Network 탭의 요청 워터폴은 서버 Traces의 분산 추적 타임라인과 같은 개념이다.
- 02
세 기둥을 먼저 분리해 보자. Logs는 개인 일기장처럼 언제, 무슨 일이, 어떻게 발생했는지를 텍스트로 남긴 이벤트 기록이다. Metrics는 건강검진 수치처럼 시간에 따라 변하는 숫자로 시스템 상태를 보여준다. Traces는 택배 추적처럼 하나의 요청이 여러 서비스를 거쳐 어디에서 멈추거나 느려졌는지 따라간다. 그래서 Logs는 무슨 일이 있었는지, Metrics는 지금 상태가 어떤지, Traces는 요청이 어디를 거쳐 갔는지에 답한다.
- 03
장애 대응에서 세 가지는 경쟁 관계가 아니라 보완 관계다. Metrics만 있으면 뭔가 잘못됐다는 사실은 알 수 있지만 어디서 왜 그런지는 알기 어렵다. Logs만 있으면 에러 메시지나 개별 이벤트는 보이지만 전체 패턴과 추세를 잡기 어렵다. Traces만 있으면 요청 경로는 보이지만 시스템 전체 건강 상태는 놓칠 수 있다. 그래서 원문이 제시하는 흐름은 Metrics로 감지하고, Logs로 원인을 좁히고, Traces로 병목 구간을 특정하는 순서다.
- 04
Logs를 제대로 쓰려면 구조화된 로그가 중요하다. 단순한 문장 로그보다 JSON 형태의 구조화 로그를 남기면 CloudWatch Log Insights 같은 도구에서 필드 기준으로 검색하고 분석하기 쉬워진다. 다만 로그 줄 전체가 유효한 JSON이어야 한다는 경계가 있다. 앞에 텍스트 프리픽스가 붙으면 CloudWatch Logs Insights가 JSON 필드로 파싱하지 못할 수 있다. 로그는 상세하지만 양이 많으므로, 검색 가능한 형태로 남기되 비용과 보존 기간도 함께 고려해야 한다.
- 05
Metrics는 시간에 따라 변하는 숫자값으로 시스템의 건강 상태를 요약한다. 원문은 구글 SRE 팀이 정의한 Golden Signals 네 가지를 핵심 지표로 제시한다. Latency는 요청 처리 시간이고, 예시는 API p99 응답 시간 500ms 미만이다. Traffic은 초당 요청 수 같은 처리량이고, Errors는 5xx 에러율 0.1% 미만 같은 에러 비율이다. Saturation은 CPU 사용률이나 큐 적체 길이처럼 시스템이 한계에 가까워지는 정도를 보여준다.
- 06
Traces는 마이크로서비스 환경에서 특히 중요하다. 하나의 요청이 여러 서비스를 지날 때, 첫 서비스가 고유한 Trace ID를 HTTP 헤더 X-Amzn-Trace-Id에 넣고, 다음 서비스로 전달될 때 이 헤더가 그대로 전파된다. 각 서비스는 자신의 처리 시간인 Span을 X-Ray 데몬에 보내고, X-Ray는 Trace ID로 Span들을 묶어 서비스 맵과 타임라인으로 보여준다. 이 구조 덕분에 느린 요청이 전체 시스템 중 어느 구간에서 지연되는지 요청 단위로 확인할 수 있다.
- 07
2025년 현재 원문이 제시하는 표준 접근은 ADOT, 즉 AWS Distro for OpenTelemetry다. ADOT는 OpenTelemetry 표준을 기반으로 하면서 X-Ray, CloudWatch, EMF와 네이티브 통합을 제공한다. 애플리케이션을 한 번 계측하면 X-Ray, CloudWatch, Prometheus 같은 여러 백엔드로 데이터를 보낼 수 있다. 프로덕션에서는 애플리케이션이 직접 백엔드에 보내기보다 Collector를 중간에 두는 중앙 집중 수집 구조가 권장된다. NestJS에서는 OpenTelemetry 자동 계측으로 Express, TypeORM, HTTP 요청, 컨트롤러 핸들러를 Span에 포함시킬 수 있다.
- 08
대표 실패 모드도 함께 기억해야 한다. 첫째, Metric Cardinality Explosion은 userId, orderId, sessionId처럼 값 종류가 계속 늘어나는 레이블을 Metric에 붙일 때 발생한다. 시계열 수가 폭발해 Collector나 Agent 메모리가 수 GB로 늘고 OOM이 날 수 있으므로, 이런 개별 식별자는 Metrics가 아니라 Logs나 Traces로 다루는 편이 맞다. 둘째, OpenTelemetry Collector의 기본 큐는 메모리 기반이어서 재시작 시 버퍼 데이터가 사라질 수 있다. 원문은 file_storage extension을 통한 영속 큐와 헬스체크, SDK 레벨 재시도를 해결 실마리로 제시한다.
- 09
로그 비용과 Trace 연결도 실무에서 자주 걸린다. DEBUG 로그가 프로덕션에 노출되거나 루프 안에서 반복 로그를 찍거나, 고빈도 헬스체크 엔드포인트까지 모두 기록하면 CloudWatch Logs나 외부 로그 수집 비용이 급증할 수 있다. 환경별 로그 레벨과 보존 기간, 샘플링이 필요하다. 또 X-Ray에서 특정 Trace를 봤는데 CloudWatch 로그를 찾을 수 없다면 로그에 Trace ID가 없기 때문일 수 있다. Trace ID를 로그에 함께 남기면 장애 시 Logs와 Traces를 서로 오가며 확인할 수 있다.
- 10
정리하면 Observability는 Logs, Metrics, Traces 중 하나를 고르는 문제가 아니라 세 관점을 연결하는 문제다. Metrics는 이상을 빠르게 감지하고, Logs는 원인 후보를 좁히며, Traces는 요청 경로 안의 병목을 특정한다. CloudWatch만 쓰고 있다면 Traces가 빠져 있는지 볼 수 있고, 로그 검색이 어렵다면 구조화 로그가 부족한지 점검할 수 있다. 우리 팀에 Logs, Metrics, Traces 중 무엇이 부족한지 아는 것 자체가 운영 개선의 출발점이다.
같은 레이어