Logs / Metrics / Traces
분류: Layer 6 - 운영 심화: 관측성 & 복원력 | 선수지식: CloudWatch Basics
1. 한 줄 정의
섹션 제목: “1. 한 줄 정의”Logs는 “무슨 일이 있었는지”, Metrics는 “상태가 어떤지”, Traces는 “요청이 어떤 경로로 흘렀는지”를 보여주는 관측 가능성(Observability)의 세 기둥이다.
2. 왜 중요한가
섹션 제목: “2. 왜 중요한가”서비스에 문제가 생겼을 때 “어디서 문제인지” 찾는 능력이 Observability이다. Logs만으로는 전체 그림이 안 보이고, Metrics만으로는 원인을 못 찾는다. 세 가지를 조합해야 장애를 빠르게 진단할 수 있다.
프론트엔드 개발자 관점에서: 브라우저 Performance API(
performance.now(),PerformanceObserver)와 Web Vitals(LCP, FID, CLS), Lighthouse 점수는 클라이언트 사이드 Observability다. 이것들은 서버 사이드의 Logs·Metrics·Traces와 정확히 대응한다 — Lighthouse의 “총 차단 시간(TBT)“은 서버 Metrics의 P99 응답 시간에 해당하고, Network 탭의 요청 워터폴은 서버 Traces의 분산 추적 타임라인과 같은 개념이다. Web Vitals를 Datadog RUM이나 CloudWatch RUM으로 수집하는 순간, 브라우저 Observability가 서버 Observability와 하나의 대시보드에서 연결된다. 프론트엔드에서 배운 “숫자로 성능을 추적한다”는 사고방식이 서버 Observability의 출발점이다.
3. 핵심 개념
섹션 제목: “3. 핵심 개념”세 기둥이 어떻게 동작하는가 (전체 흐름)
섹션 제목: “세 기둥이 어떻게 동작하는가 (전체 흐름)”비유:
- Logs: 개인 일기장 — “오늘 오전 10시에 결제가 실패했다. 에러 메시지는 TimeoutException이었다.”
- Metrics: 건강검진 수치 — “혈압 130/90, 심박수 88 (정상 범위 초과)”
- Traces: 택배 추적 — “물류센터→허브→지역센터→배달 중 — 현재 지역센터에서 3시간째 멈춰있음”
동작 원리: 장애 진단 시 세 기둥을 사용하는 순서
1단계: Metrics 확인 (이상 감지) → CloudWatch 대시보드: "에러율이 급증했다, 응답 시간이 5초를 넘어섰다" → "무언가 문제가 있다"는 것을 숫자로 확인
2단계: Logs 확인 (원인 파악) → CloudWatch Log Insights로 ERROR 로그 검색 → "DB connection refused" 에러가 반복되고 있음을 발견
3단계: Traces 확인 (병목 위치 특정) → AWS X-Ray 서비스 맵에서 요청 경로 시각화 → API → OrderService → DB 호출이 4.5초 → DB가 병목임을 확인왜 세 가지가 모두 필요한가 — 설계 철학
Metrics만 있으면 “뭔가 잘못됐다”는 것은 알지만 “어디서 왜”는 모른다. Logs만 있으면 개별 이벤트는 알지만 전체 패턴(추세)을 보기 어렵다. Traces만 있으면 요청 경로는 알지만 시스템 전체 건강 상태를 모른다. 이 세 가지는 서로를 보완하는 관계다 — “감지(Metrics) → 좁히기(Logs) → 특정(Traces)” 순서가 최적의 장애 대응 흐름이다.
ADOT(AWS Distro for OpenTelemetry) — 2025년 표준 접근법
2025년 현재 AWS는 ADOT(AWS Distro for OpenTelemetry)를 공식 권장한다. ADOT는 OpenTelemetry 표준을 기반으로 하되, AWS 서비스(X-Ray, CloudWatch, EMF)와의 네이티브 통합을 제공한다. 애플리케이션을 한 번 계측하면 여러 백엔드(X-Ray, CloudWatch, Prometheus)로 동시에 데이터를 전송할 수 있다.
ADOT Collector 구성 (ECS Fargate 사이드카):NestJS App → ADOT Collector (사이드카) ├── Traces → AWS X-Ray ├── Metrics → Amazon Managed Prometheus └── Logs → CloudWatch Logs
장점: 앱 코드를 바꾸지 않고 백엔드를 교체/추가할 수 있음📖 더 보기: OpenTelemetry 공식 문서: Observability Primer — 위 3단계 진단 흐름의 이론적 배경과 각 신호(Signal)가 언제 유용한지 설명
Logs (로그)
이벤트 기록. “언제, 무슨 일이, 어떻게 발생했는지”를 텍스트로 남김.
구조화된 로그(JSON) vs 비구조화된 로그 비교:
# 비구조화된 로그 (검색/분석 어려움)2026-03-28 10:30:00 ERROR payment failed: timeout after 5000ms for user 12345
# 구조화된 로그 JSON (CloudWatch Log Insights로 바로 쿼리 가능){ "timestamp": "2026-03-28T10:30:00.123Z", "level": "ERROR", "event": "payment_failed", "userId": "12345", "orderId": "order-9821", "error": "TimeoutException", "durationMs": 5000, "service": "payment-service"}구조화된 로그를 쓰면 Log Insights에서 이런 쿼리가 가능:
// userId별 에러 건수 집계filter level = "ERROR"| stats count(*) as errorCount by userId| sort errorCount desc| limit 10NestJS에서 구조화된 로그 구현 (Winston 사용):
// npm install winston nest-winstonimport { WinstonModule } from "nest-winston";import * as winston from "winston";
export const loggerConfig = WinstonModule.forRoot({ transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), winston.format.json(), // JSON 구조화 출력 ), }), ],});
// 사용 예시this.logger.error("payment_failed", { userId: "12345", orderId: "order-9821", error: error.message, durationMs: Date.now() - startTime,});
// 출력 예상:// {"timestamp":"2026-03-28T10:30:00.123Z","level":"error","message":"payment_failed","userId":"12345",...}📖 더 보기: NestJS 로그 프로 레벨 가이드 - Medium — Correlation ID로 요청 단위 로그 추적까지 연결하는 방법 설명
Metrics (지표)
시간에 따라 변하는 숫자값. 시스템의 건강 상태를 보여줌.
Golden Signals — 구글 SRE 팀이 정의한 4가지 핵심 지표:
| Signal | 설명 | 예시 |
|---|---|---|
| Latency | 요청 처리 시간 | API p99 응답 시간 < 500ms |
| Traffic | 처리량 (요청 수/초) | RPS(초당 요청 수) |
| Errors | 에러 비율 | 5xx 에러율 < 0.1% |
| Saturation | 시스템 포화도 (한계 가까움) | CPU 사용률, 큐 적체 길이 |
AWS에서 Golden Signals 확인 경로:- Latency: CloudWatch → ALB → TargetResponseTime- Traffic: CloudWatch → ALB → RequestCount- Errors: CloudWatch → ALB → HTTPCode_Target_5XX_Count- Saturation: CloudWatch → ECS → CPUUtilizationTraces (추적)
하나의 요청이 여러 서비스를 거치는 전체 경로를 추적. 마이크로서비스 환경에서 중요.
Trace와 Span의 관계:
Trace (전체 요청 1건, 고유 Trace ID)├── Span: API Gateway 처리 (2ms)├── Span: OrderService 처리 (23ms)│ ├── Span: DB 쿼리 - SELECT orders (18ms) ← 병목!│ └── Span: 캐시 조회 (2ms)└── Span: NotificationService 처리 (5ms)총 소요: 30msAWS X-Ray 동작 방식:
- 첫 서비스(API Gateway 등)가 고유한 Trace ID를 HTTP 헤더(
X-Amzn-Trace-Id)에 삽입 - 요청이 다음 서비스로 전달될 때 이 헤더가 그대로 전파 (Context Propagation)
- 각 서비스가 자신의 처리 시간(Span)을 X-Ray 데몬에 전송
- X-Ray가 Trace ID로 Span들을 묶어서 서비스 맵과 타임라인 시각화
X-Amzn-Trace-Id 헤더 예시:X-Amzn-Trace-Id: Root=1-5e811a77-c7f3e5b3af48b5e5cf8e44a5;Parent=82b7b940e9d7e5c1;Sampled=1 ↑ Trace ID ↑ 현재 Span ID📖 더 보기: AWS X-Ray 핵심 개념 공식 문서 — 위 Trace/Span/Segment 구조와 Sampling 설정 방법 상세 설명
Observability의 3 기둥 비유
| Logs | Metrics | Traces | |
|---|---|---|---|
| 비유 | 일기장 | 건강검진 수치 | 택배 추적 |
| 질문 | ”무슨 일이 있었어?" | "지금 상태가 어때?" | "요청이 어디를 거쳐갔어?” |
| 특징 | 상세하지만 양이 많음 | 요약된 숫자, 추세 파악 | 요청 단위 전체 경로 |
대표 도구
- Logs: CloudWatch Logs, ELK(Elasticsearch), Loki
- Metrics: CloudWatch Metrics, Prometheus, Datadog
- Traces: X-Ray(AWS), Jaeger, Datadog APM
실전 아키텍처 패턴
섹션 제목: “실전 아키텍처 패턴”패턴 1: Collector 기반 중앙 집중 수집
2025년 현재 프로덕션에서 권장되는 구조. 애플리케이션이 직접 백엔드에 데이터를 보내지 않고, Collector(수집기) 를 중간에 두는 방식이다.
NestJS App ↓ (로컬 전송 - 빠름)OpenTelemetry Collector (사이드카 or DaemonSet) ├── Logs → CloudWatch Logs ├── Metrics → CloudWatch Metrics / Prometheus └── Traces → AWS X-Ray / Jaeger
장점:- 앱이 재시도/배칭/필터링을 직접 안 해도 됨- 백엔드 교체 시 앱 코드 수정 불필요 (Collector 설정만 변경)- 민감 데이터 필터링을 Collector 레벨에서 처리패턴 2: NestJS OpenTelemetry 자동 계측
NestJS는 OpenTelemetry 자동 계측 라이브러리를 지원한다. 별도 코드 없이 Express, TypeORM, HTTP 요청 등을 자동으로 Trace에 포함시킨다.
// npm install @opentelemetry/api @opentelemetry/sdk-node// npm install @opentelemetry/auto-instrumentations-node// npm install @opentelemetry/exporter-trace-otlp-http
// tracing.ts - main.ts보다 먼저 실행되어야 함!import { NodeSDK } from "@opentelemetry/sdk-node";import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces", // OpenTelemetry Collector 주소 }), instrumentations: [ getNodeAutoInstrumentations({ // TypeORM, Express, HTTP 요청 자동 계측 "@opentelemetry/instrumentation-typeorm": { enabled: true }, }), ],});
sdk.start(); // main.ts NestFactory.create() 전에 호출
// main.ts// import './tracing'; ← 최상단에서 먼저 import// async function bootstrap() { ... }예상 동작: TypeORM 쿼리, 외부 HTTP 요청, NestJS 컨트롤러 핸들러가 자동으로 Span으로 기록됨. AWS X-Ray나 Jaeger에서 요청 경로를 한눈에 확인 가능.
⚠️ 가장 흔한 실수 - SDK 초기화 순서:
// ❌ 잘못된 순서: main.ts 내부에서 sdk.start() 호출// NestFactory.create()가 먼저 실행되면 Express, TypeORM 계측이 누락됨async function bootstrap() { sdk.start(); // 이미 늦음! const app = await NestFactory.create(AppModule);}
// ✅ 올바른 순서: tracing.ts를 main.ts의 최상단에서 import// main.tsimport "./tracing"; // ← 반드시 첫 번째 importimport { NestFactory } from "@nestjs/core";// ...Collector가 없으면 앱이 재시도/배칭을 직접 처리해야 하므로, 프로덕션에서는 Collector를 반드시 두는 것이 2025년 현재의 표준 패턴이다.
📖 더 보기: OpenTelemetry NestJS 구현 가이드 2026 - SigNoz — NestJS에 OpenTelemetry SDK 설치부터 AWS X-Ray 연동까지, 초기화 순서 문제와 Collector 설정 포함 (중급)
패턴 3: 온라인 쇼핑몰 실제 적용 사례
한 온라인 쇼핑몰 팀이 구조화된 로깅으로 주문 처리 속도 5% 저하를 발견한 실제 사례:
발견 과정:1. CloudWatch 대시보드에서 주문 API p99 응답시간 증가 감지 (Metrics)2. Log Insights로 특정 시간대 "order_processing_slow" 로그 급증 발견 (Logs)3. X-Ray 트레이스에서 DB SELECT 쿼리가 전체 처리시간의 80% 차지 확인 (Traces)4. 원인: 누락된 인덱스 → 인덱스 추가 후 서버 설정 최적화 → 처리속도 10% 향상
핵심 교훈: Metrics(감지) → Logs(좁히기) → Traces(특정) 순서로 진단4. 실무에서 어디에 쓰이나
섹션 제목: “4. 실무에서 어디에 쓰이나”- 장애 발생: Metrics로 이상 감지 → Logs로 원인 파악 → Traces로 병목 구간 특정
- 성능 최적화: Metrics로 느린 구간 발견 → Traces로 상세 분석
- 운영 대시보드: Metrics 기반으로 팀 서비스 상태 한눈에 확인
5. 현재 내 업무와 연결점
섹션 제목: “5. 현재 내 업무와 연결점”- 장애 대응 시 “뭘 먼저 봐야 하는지” 판단 기준이 됨
- 모니터링 개선 제안 시 “Logs/Metrics/Traces 중 뭐가 부족한지” 진단
- CloudWatch만 쓰고 있다면, Traces 도입의 필요성 제안 가능
6. 자주 헷갈리는 개념 비교
섹션 제목: “6. 자주 헷갈리는 개념 비교”| 개념 A | 개념 B | 차이점 |
|---|---|---|
| Logs | Metrics | Logs는 개별 이벤트 텍스트, Metrics는 집계된 숫자 |
| Metrics | Traces | Metrics는 전체 시스템 요약, Traces는 개별 요청의 상세 경로 |
| Monitoring | Observability | Monitoring은 “알려진 문제 감시”, Observability는 “모르는 문제도 진단 가능” |
| Structured Log | Unstructured Log | Structured(JSON)는 검색/분석 가능, Unstructured(텍스트)는 사람만 읽기 편함 |
| OpenTelemetry | X-Ray SDK | OpenTelemetry는 벤더 중립 표준, X-Ray SDK는 AWS 전용 (OTel이 장기적으로 권장) |
6.2. 클라우드 중립 Observability 원칙
섹션 제목: “6.2. 클라우드 중립 Observability 원칙”AWS 중심 예시가 많지만, 핵심 원칙은 어느 플랫폼에서도 동일하다. 도구가 바뀌어도 아래 원칙은 변하지 않는다:
| 원칙 | AWS 구현 | GCP 구현 | 도구 무관 원칙 |
|---|---|---|---|
| 구조화 로그 | CloudWatch JSON 파싱 | Cloud Logging JSON | 로그 줄 전체가 JSON이어야 자동 파싱 |
| Golden Signals | CloudWatch ALB 지표 | Cloud Monitoring SLI | Latency·Traffic·Errors·Saturation 4개는 어디서나 동일 |
| 분산 추적 전파 | X-Amzn-Trace-Id 헤더 | X-Cloud-Trace-Context 헤더 | W3C TraceContext 표준(traceparent)이 벤더 중립 |
| Collector 패턴 | ADOT(AWS Distro for OTel) | Cloud Ops Agent | 앱→Collector→백엔드 3계층 분리 |
| 샘플링 전략 | X-Ray Sampling Rules | Cloud Trace sampling | Head-based(시작점 결정) vs Tail-based(완료 후 결정) |
클라우드를 이동해도 바꾸지 않아도 되는 것: OpenTelemetry SDK 코드, 구조화 로그 포맷(JSON), Golden Signals 알람 임계값, Collector 배포 패턴. 바꿔야 하는 것: Collector 백엔드 설정(X-Ray endpoint → Cloud Trace endpoint), IAM/IAP 인증 설정.
6.3. 도구 선택 의사결정 프레임워크
섹션 제목: “6.3. 도구 선택 의사결정 프레임워크”OTel SDK vs X-Ray SDK
섹션 제목: “OTel SDK vs X-Ray SDK”중요: AWS는 2026-02-25부터 X-Ray SDK/Daemon을 maintenance mode로 전환하고 2027-02-25에 end-of-support 예정. 신규 프로젝트는 OpenTelemetry 계측이 사실상 강제이다. (AWS 공식 발표)
| 기준 | OpenTelemetry SDK | AWS X-Ray SDK |
|---|---|---|
| 벤더 종속성 | 없음 (CNCF 표준) | AWS 전용 |
| 멀티클라우드 지원 | 가능 (Collector 백엔드 교체) | 불가 |
| 팀 규모 | 소규모~대규모 모두 적합 | 레거시 AWS 전용 팀 |
| 마이그레이션 | 기존 X-Ray 콘솔 유지 가능 | — |
| 결론 | 신규 프로젝트 기본 선택 | 레거시 유지 외 비권장 |
Prometheus vs CloudWatch Metrics
섹션 제목: “Prometheus vs CloudWatch Metrics”| 기준 | Prometheus (Amazon Managed) | CloudWatch Metrics |
|---|---|---|
| 배포 환경 | 컨테이너(EKS/ECS) 중심 | AWS 전 서비스 통합 |
| 레이블 카디널리티 | 최대 150개 레이블 지원 | 커스텀 지표 최대 30 dimensions |
| 쿼리 언어 | PromQL (유연, 표현력 높음) | CloudWatch Metrics Insights (제한적) |
| 비용 구조 | 수집 sample 수 기반 | 지표 수 + API 호출 수 기반 |
| 대시보드 | Grafana 연동 (오픈소스 생태계) | CloudWatch Dashboard (AWS 통합) |
| 선택 기준 | Kubernetes 워크로드 + PromQL 필요 시 | AWS 단일 클라우드 + 통합 관리 우선 시 |
OTel Collector 배포 방식: 사이드카 vs DaemonSet
섹션 제목: “OTel Collector 배포 방식: 사이드카 vs DaemonSet”| 기준 | 사이드카 (Sidecar) | DaemonSet |
|---|---|---|
| 격리 수준 | 높음 (Pod 단위 독립) | 낮음 (Node 단위 공유) |
| 리소스 오버헤드 | 높음 (Pod마다 Collector) | 낮음 (Node당 1개) |
| 설정 유연성 | Pod별 다른 설정 가능 | 단일 설정 공유 |
| 선택 기준 | 보안 민감 서비스, 설정 격리 필요 시 | 일반 마이크로서비스, 리소스 절약 우선 시 |
6.4. Sampling 전략 설계 원칙
섹션 제목: “6.4. Sampling 전략 설계 원칙”Trace를 100% 수집하면 비용이 급증한다. 트래픽 볼륨과 비용 목표에 따라 sampling 비율을 설계해야 한다.
Head-based vs Tail-based Sampling
섹션 제목: “Head-based vs Tail-based Sampling”| 구분 | Head-based Sampling | Tail-based Sampling |
|---|---|---|
| 결정 시점 | 요청 시작 시점 (첫 Span) | 모든 Span 완료 후 |
| 에러/고지연 포착 | 불가 (무작위 선택) | 가능 (조건부 필터) |
| 인프라 비용 | 낮음 | 높음 (Collector 메모리 필요) |
| 구현 복잡도 | 단순 | 복잡 (stateful Collector) |
| 적합한 상황 | 저트래픽, 균일한 요청 | 에러/SLO 위반 트레이스 보존 필수 |
Sampling 비율 결정 가이드
섹션 제목: “Sampling 비율 결정 가이드”비율 결정 공식 (Head-based 기준): 목표 월 비용 / (트레이스 단가 × 월 총 요청 수) = 목표 sampling 비율
예시: 월 트레이스 예산 $50, X-Ray 단가 $5/백만, 월 트래픽 1억 건 → $50 / ($5 × 100) = 10% samplingAWS X-Ray 기본 샘플링 규칙 (Default Rule):
- Reservoir: 초당 1건 고정 수집 (무조건 기록)
- Fixed Rate: Reservoir 초과분의 5%만 기록
- 결과: 로컬 개발 환경에서 trace가 거의 안 보이는 원인 — 초당 1건 + 5%는 매우 적음
- 변경 방법: X-Ray 콘솔 → Sampling rules → Default rule 수정
6.5. 트러블슈팅
섹션 제목: “6.5. 트러블슈팅”🔧 Metric Cardinality Explosion — 메모리가 급증한다
섹션 제목: “🔧 Metric Cardinality Explosion — 메모리가 급증한다”증상: Prometheus 또는 CloudWatch 지표 수집 후 Collector/Agent 메모리가 수 GB로 급증하고 OOM(Out of Memory) 발생
원인: 고카디널리티 레이블 사용. userId, orderId, sessionId 등 값의 종류가 무한히 증가하는 레이블을 Metric에 붙이면 시계열(time series) 수가 폭발적으로 증가함. 예: http_request_total{userId="12345"} → 사용자 수만큼 시계열 생성
해결:
-
고카디널리티 레이블을 Metric에서 제거하고, 개별 식별자가 필요하다면 Logs 또는 Traces를 사용
# ❌ 카디널리티 폭발 유발http_request_total{userId="12345", orderId="order-9821"}# ✅ 올바른 설계 — 분류 가능한 값만 레이블로http_request_total{endpoint="/api/orders", status="500"} -
Prometheus scrape config에
sample_limit을 설정해 폭발 시 스크랩 중단:scrape_configs:- job_name: "my-service"sample_limit: 10000 # 시계열 10,000개 초과 시 스크랩 거부 -
이미 발생했다면: Prometheus 재시작 → 문제 레이블 제거 → 재수집. CloudWatch라면 해당 커스텀 지표를 삭제하고 레이블 구조 재설계
-
레이블 카디널리티 가이드라인: 지표 1개당 레이블 조합 10개 미만 유지. 불가피한 경우 최대 몇 십 개 이내
🔧 OpenTelemetry Collector 크래시 시 데이터 유실
섹션 제목: “🔧 OpenTelemetry Collector 크래시 시 데이터 유실”증상: OTel Collector 사이드카/DaemonSet이 재시작되면서 수집 중이던 Trace/Metrics 데이터가 유실됨 원인: Collector의 기본 큐(queue)는 메모리 기반이므로, 프로세스 재시작 시 버퍼 내 데이터가 모두 사라짐 해결:
- Collector 설정에
file_storageextension으로 영속 큐 활성화:extensions:file_storage:directory: /var/lib/otelcol # 호스트 볼륨 마운트 필요exporters:otlphttp:sending_queue:storage: file_storagenum_consumers: 10queue_size: 10000 - Collector 자체 헬스체크 엔드포인트(
/health/status)를 Liveness Probe로 등록해 빠른 재시작 보장 - Collector 다운 시 앱이 데이터를 잃지 않도록 SDK 레벨에서도 재시도 설정:
new OTLPTraceExporter({url: "http://localhost:4318/v1/traces",timeoutMillis: 30000,});// OTel SDK는 기본적으로 5회 지수 백오프 재시도
🔧 Log Ingestion 비용 폭탄 — 즉각 대응 절차
섹션 제목: “🔧 Log Ingestion 비용 폭탄 — 즉각 대응 절차”증상: CloudWatch Logs 또는 외부 로그 수집 서비스 청구액이 예상의 10배 이상으로 급증 원인: DEBUG 로그 프로덕션 노출, 루프 내 반복 로그, 고빈도 헬스체크 엔드포인트 로그 즉각 대응 절차:
Step 1. 비용 원인 식별 (5분 이내) → CloudWatch → Cost Explorer → 서비스별 필터링 → 또는 Log Insights 쿼리로 Log Group별 수집량 확인: stats sum(@ingestionBySizeBytes) as bytes by @logStream | sort bytes desc | limit 20
Step 2. 임시 억제 → 비용 유발 Log Group의 로그 레벨을 WARN으로 상향 (환경변수 변경) → 헬스체크 경로(/health, /ping) 로그 필터 제외: NestJS 미들웨어에서 healthcheck 요청 제외 후 재배포
Step 3. 구조적 해결 → 로그 보존 기간 단축 (무제한 → 30일) → 고빈도 이벤트는 Metrics로 전환 (로그 대신 카운터 증가) → 샘플링: 1분에 같은 에러가 100건 이상이면 첫 10건만 로그🔧 CloudWatch Log Insights에서 JSON 필드를 검색할 수 없다
섹션 제목: “🔧 CloudWatch Log Insights에서 JSON 필드를 검색할 수 없다”증상: 로그가 JSON 형식인데 filter userId = "12345" 쿼리가 동작하지 않음
원인: CloudWatch Logs Insights는 JSON 로그를 자동 파싱하지만, 로그가 완전한 JSON 형식이 아니거나 앞에 텍스트가 붙으면 파싱 실패
해결:
- 로그 형식 확인: 전체 줄이
{...}JSON이어야 함.[INFO] {"userId":...}처럼 앞에 텍스트가 있으면 파싱 안 됨 - 로그 출력 코드에서
console.log(JSON.stringify(obj))형태로 순수 JSON만 출력하도록 수정 - Log Insights에서
parse @message명령으로 수동 파싱:parse @message '* {"userId": "*"' as prefix, userId| filter userId = "12345"
🔧 X-Ray Trace가 표시되지 않는다
섹션 제목: “🔧 X-Ray Trace가 표시되지 않는다”증상: X-Ray 서비스 맵에 서비스가 보이지 않거나 Trace가 0건
원인 1: ECS Task Role에 xray:PutTraceSegments, xray:PutTelemetryRecords 권한이 없음
원인 2: X-Ray SDK 또는 X-Ray 데몬(사이드카 컨테이너)이 실행되지 않음
해결:
- ECS Task IAM Role → 정책 확인 →
AWSXRayDaemonWriteAccess정책 추가 - ECS Task Definition에 X-Ray 데몬 사이드카 컨테이너 추가:
{"name": "xray-daemon","image": "amazon/aws-xray-daemon","portMappings": [{ "containerPort": 2000, "protocol": "udp" }]}
- Sampling 설정 확인 — 기본값은 초당 1건 + 5%만 샘플링하므로 로컬에선 거의 안 보일 수 있음
🔧 로그가 너무 많아서 CloudWatch 비용이 과다 발생한다
섹션 제목: “🔧 로그가 너무 많아서 CloudWatch 비용이 과다 발생한다”증상: CloudWatch Logs 요금이 예상보다 수십 배 청구됨 원인: DEBUG/INFO 레벨 로그를 프로덕션에서도 그대로 출력하거나, 루프 내에서 매 반복마다 로그를 찍는 구조 해결:
- 환경별 로그 레벨 설정 — 프로덕션은
WARN이상만 출력// NestJS - 환경변수로 로그 레벨 제어const app = await NestFactory.create(AppModule, {logger:process.env.NODE_ENV === "production"? ["warn", "error"]: ["log", "debug", "error"],}); - CloudWatch Logs → Log Groups → Retention 설정 (기본값 무기한 → 30일로 변경)
- 실무 팁: 비즈니스 중요도에 따른 계층화 보존 전략 적용
- 프로덕션 에러 로그: 7일 즉시 접근 + 90일 보존
- 일반 INFO 로그: 3일 보존
- 스테이징 디버그 로그: 24시간만 보존
- 이 전략으로 CloudWatch 로깅 비용 최대 30% 절감 효과 보고됨
- 실무 팁: 비즈니스 중요도에 따른 계층화 보존 전략 적용
- 고빈도 이벤트(헬스체크 등)는 로그 제외하거나 샘플링
🔧 OpenTelemetry 계측 후 앱 시작 속도가 느려졌다
섹션 제목: “🔧 OpenTelemetry 계측 후 앱 시작 속도가 느려졌다”증상: OpenTelemetry SDK 추가 후 NestJS 앱 부팅 시간이 2~3초 늘어남
원인: getNodeAutoInstrumentations()가 모든 라이브러리를 계측 대상으로 로드하는 과정에서 초기화 오버헤드 발생
해결:
- 사용하지 않는 계측 라이브러리를 명시적으로 비활성화:
getNodeAutoInstrumentations({"@opentelemetry/instrumentation-fs": { enabled: false }, // 파일 시스템 계측 제외"@opentelemetry/instrumentation-dns": { enabled: false },});
tracing.ts를main.ts최상단에서 import해야 함 — NestFactory.create() 이후에 호출하면 계측이 누락됨
🔧 Trace ID가 로그와 연결되지 않아 장애 시 추적이 어렵다
섹션 제목: “🔧 Trace ID가 로그와 연결되지 않아 장애 시 추적이 어렵다”증상: X-Ray에서 특정 Trace를 발견했는데, 해당 요청의 CloudWatch 로그를 찾을 수 없음 원인: 로그에 Trace ID가 포함되어 있지 않아서 로그와 Trace를 상호 참조할 수 없음 해결:
-
Winston 로거에 Trace ID를 자동 포함하는 설정 추가:
import { context, trace } from "@opentelemetry/api";// 커스텀 Winston format으로 Trace ID 자동 추가const addTraceId = winston.format((info) => {const span = trace.getActiveSpan();if (span) {const { traceId } = span.spanContext();info.traceId = traceId;}return info;});// 로그 출력:// {"timestamp":"...","level":"error","message":"payment_failed","traceId":"1-5e811a77-..."} -
이렇게 하면 CloudWatch Log Insights에서
filter traceId = "1-5e811a77-..."로 해당 요청 로그만 추출 가능
🔧 구조화 로그(JSON)가 CloudWatch에서 파싱되지 않는다
섹션 제목: “🔧 구조화 로그(JSON)가 CloudWatch에서 파싱되지 않는다”증상: NestJS 앱에서 Winston으로 JSON 로그를 출력하는데, Log Insights에서 { $.level = "ERROR" } 패턴 검색이 안 됨
원인: CloudWatch Log Insights는 로그 줄 전체가 유효한 JSON이어야 JSON 필드로 파싱한다. NestJS 기본 로거나 일부 Winston 설정은 [Nest] 2026-04-08 ERROR [AppModule] something failed 처럼 앞에 텍스트 프리픽스가 붙어서 전체가 JSON이 아닌 형태로 전송됨
해결:
-
Winston JSON 포맷 설정 확인 — 앞에 어떤 텍스트도 없이 순수 JSON이어야 함:
// ❌ 잘못된 설정: 텍스트 + JSON 혼합 출력format: winston.format.combine(winston.format.timestamp(),winston.format.printf(({ level, message, timestamp }) => `${timestamp} ${level}: ${message}`, // JSON 아님),);// ✅ 올바른 설정: 순수 JSON 출력format: winston.format.combine(winston.format.timestamp(),winston.format.json(), // 전체 줄이 JSON);// 출력 예: {"timestamp":"2026-04-08T10:00:00Z","level":"error","message":"payment_failed"} -
CloudWatch Log Insights에서 파싱 성공 여부 검증:
# JSON 필드 기반 검색 (파싱 성공 시 동작)filter level = "ERROR"| fields @timestamp, level, message, userId| sort @timestamp desc| limit 20 -
@message필드에 프리픽스 텍스트가 포함된 경우 정규식으로 우회:# JSON 파싱이 안 되는 경우 텍스트 검색으로 대체filter @message like /ERROR/
7. 체크리스트
섹션 제목: “7. 체크리스트”- Logs, Metrics, Traces의 차이를 각각 한 문장으로 설명할 수 있다
- 장애 발생 시 어떤 순서로 이 세 가지를 확인하는지 설명할 수 있다
- 팀 서비스에서 Logs/Metrics/Traces 중 뭘 쓰고 있고 뭐가 부족한지 안다
- Structured Log(JSON)와 Unstructured Log의 차이와 장단점을 설명할 수 있다
- OpenTelemetry가 X-Ray SDK와 어떻게 다른지 설명할 수 있다
8. 추가 학습 키워드
섹션 제목: “8. 추가 학습 키워드”OpenTelemetry, SLI/SLO/SLA, 에러 버짓, Golden Signals(Latency, Traffic, Errors, Saturation), Alerting 전략, Collector 패턴, Correlation ID
8.5. 추천 리소스
섹션 제목: “8.5. 추천 리소스”- 📖 OpenTelemetry 공식 문서: Observability Primer — Logs/Metrics/Traces 각 신호의 차이와 언제 무엇을 써야 하는지 명쾌하게 정리 (입문)
- 📖 AWS X-Ray 핵심 개념 - AWS 공식 문서 — Trace, Segment, Span, Sampling 등 X-Ray 동작 원리 상세 (중급)
- 📖 NestJS 구조화 로깅 가이드 - Medium — Winston + Correlation ID로 프로덕션 수준의 NestJS 로깅 구현 (중급)
- 🎬 CloudWatch 구조화 로그 가속 디버깅 - AWS 블로그 — JSON 구조화 로그로 Log Insights 쿼리를 극대화하는 실전 가이드 (중급)
- 📖 OpenTelemetry NestJS 구현 가이드 - SigNoz — NestJS에 OpenTelemetry SDK 설치부터 AWS X-Ray 연동까지 단계별 가이드 (중급)
9. 내가 직접 확인해볼 것
섹션 제목: “9. 내가 직접 확인해볼 것”- 팀 서비스의 모니터링 현황 파악 (Logs/Metrics/Traces 각각 뭘 쓰는지)
- CloudWatch에서 Metrics 대시보드 확인
경로:
CloudWatch → Dashboards또는Metrics → All metrics확인: Golden Signals 4개(Latency, Traffic, Errors, Saturation)가 있는지 체크 - 에러 발생 시 Logs → Metrics → Traces 순으로 추적해보기
Log Insights 쿼리:
예상 출력: 5분 단위로 에러 발생 건수가 집계된 테이블filter level = "ERROR"| stats count(*) as cnt by bin(5m)| sort @timestamp desc
- 부족한 부분이 있다면 개선 제안 초안 작성
10. 5줄 요약
섹션 제목: “10. 5줄 요약”- Logs(이벤트 기록), Metrics(상태 수치), Traces(요청 경로)는 Observability의 3 기둥이다
- 장애 대응: Metrics로 감지 → Logs로 원인 → Traces로 병목 특정
- 하나만으로는 부족하고, 세 가지를 조합해야 문제를 빠르게 진단할 수 있다
- 구조화된 로그(JSON)를 쓰면 검색과 분석이 훨씬 쉬워진다
- “우리 팀에 뭐가 부족한지” 아는 것 자체가 운영 개선의 출발점이다
11. 실전 장애 대응 시나리오 (On-Call Runbook)
섹션 제목: “11. 실전 장애 대응 시나리오 (On-Call Runbook)”실제 on-call 호출 시 따라가는 체크리스트. 서비스 응답이 느리거나 에러율이 올라갈 때 사용.
시나리오 A: “API 에러율 급증” 알람이 울렸을 때
섹션 제목: “시나리오 A: “API 에러율 급증” 알람이 울렸을 때”Step 1. 규모 파악 (1분 이내) → CloudWatch 대시보드 확인 → 질문: "어떤 API에서? 얼마나? 언제부터?" → ALB 지표: HTTPCode_Target_5XX_Count / RequestCount = 에러율 → 5% 이상이면 즉시 대응, 1% 미만이면 관찰 지속
Step 2. 에러 내용 확인 (3분 이내) → CloudWatch Log Insights 쿼리: filter level = "ERROR" | stats count(*) as cnt by errorType, @timestamp | sort cnt desc | limit 20 → 질문: "어떤 에러 메시지가 반복되는가?" → DB 연결 오류 → RDS 상태 확인 → TimeoutException → 외부 API 또는 DB 쿼리 시간 확인
Step 3. 장애 범위 좁히기 (5분 이내) → X-Ray 서비스 맵에서 어느 서비스/DB가 빨간색인지 확인 → 특정 Trace ID의 전체 경로 확인: 어느 Span에서 오래 걸리는가 → 질문: "내부 문제인가, 외부 의존성 문제인가?"
Step 4. 임시 조치 / 에스컬레이션 → 내부 DB 문제: RDS 재시작 / 읽기 전용 복제본으로 트래픽 이동 → 외부 API 문제: Circuit Breaker 작동 여부 확인, 해당 기능 일시 비활성화 → 해결 불가 시 에스컬레이션 + Slack 알림에 현황 요약 공유시나리오 B: “Trace가 있는데 해당 로그를 못 찾을 때”
섹션 제목: “시나리오 B: “Trace가 있는데 해당 로그를 못 찾을 때””증상: X-Ray에서 Trace ID를 찾았는데 CloudWatch에서 연결된 로그가 없음
해결 체크리스트:1. 로그에 traceId 필드가 포함되어 있는지 확인 → Log Insights: filter @message like /traceId/2. 포함되어 있다면 해당 traceId로 검색: → filter traceId = "1-5e811a77-..."3. 없다면 Winston 로거에 Trace ID 자동 주입 설정 필요 → 섹션 6.5 "Trace ID가 로그와 연결되지 않아" 항목 참고4. 시간대 불일치: X-Ray Trace 시간 기준 ±5분으로 Log Insights 시간 범위 확장시나리오 C: “갑자기 CloudWatch 비용이 5배 올랐을 때”
섹션 제목: “시나리오 C: “갑자기 CloudWatch 비용이 5배 올랐을 때””즉시 확인할 것:1. AWS Cost Explorer → CloudWatch → 어느 항목이 급증했는지 확인 (Logs Ingestion / Logs Storage / Log Insights 쿼리 중 어느 것?)
2. Logs Ingestion 급증이면: → Log Groups 목록에서 최근 24시간 Ingested Bytes 내림차순 정렬 → 의심 Log Group 발견 시 해당 서비스 코드 확인 (루프 내 로그, DEBUG 레벨 등)
3. Log Insights 쿼리 과금 급증이면: → 자동화 스크립트나 대시보드가 너무 자주 쿼리를 실행하는지 확인 → 반복 쿼리는 Metric Filter로 대체
4. 즉시 조치: Retention 없는 Log Group에 30일 이하 설정 → CloudWatch → Log groups → Retention 컬럼에서 "Never expire" 항목 찾아 설정시나리오 D: 같은 장애가 반복될 때 — 구조적 원인 분석
섹션 제목: “시나리오 D: 같은 장애가 반복될 때 — 구조적 원인 분석”단순 “증상→해결” 패턴으로는 반복 장애를 막을 수 없다. 구조적 원인을 찾는 프레임워크:
반복 장애 구조적 분석 절차──────────────────────────Step 1. 패턴 분류 (같은 증상이 월 2회 이상이면 구조적 문제) → CloudWatch Log Insights: filter level = "ERROR" | stats count(*) by errorType, bin(1d) | sort errorType, bin → "DB connection refused"가 매주 월요일 09:00에 반복된다 → 패턴 있음
Step 2. 공통 선행 조건 찾기 (5 Whys 적용) → "왜 DB 연결이 거부됐나?" → 커넥션 풀 소진 → "왜 커넥션 풀이 소진됐나?" → 월요일 09:00 배치 잡 실행 → "왜 배치 잡이 커넥션을 과점했나?" → timeout 설정 없이 장시간 쿼리 → "왜 timeout 설정이 없었나?" → 코드 리뷰에서 DB 연결 설정 항목이 없었음 → "왜 리뷰 항목이 없었나?" → 체크리스트 부재 (구조적 원인)
Step 3. 구조 수정 (증상 치료가 아닌 시스템 변경) → 배치 잡에 커넥션 timeout 30초 추가 (증상 치료) → PR 체크리스트에 "DB 연결 timeout 설정 확인" 추가 (구조 변경) → 커넥션 풀 사용률 Metric + 알람 추가 (조기 감지 구조)
핵심 원칙: 구조적 수정은 "알람 추가"가 아니라 "이 패턴이 다시 발생할 수 없는 구조를 만드는 것"이다.2025년 최신 동향
섹션 제목: “2025년 최신 동향”Shift-Left Observability
2025년 현재, Observability는 운영팀 전담에서 개발팀 공동 책임으로 전환되는 추세다. 개발자가 코드를 작성할 때부터 “이 코드가 프로덕션에서 어떻게 관측될지”를 고려하는 것이 권장 방향이다.
- 각 알람에는 반드시 Runbook 링크가 포함되어야 한다
- 알람 메시지에 “어떤 증상인지”, “비즈니스 영향이 무엇인지”가 명시되어야 한다
- 개발자가 자신이 만든 서비스의 모니터링 설정과 대응 절차를 직접 작성하는 문화
OpenTelemetry 표준화 가속
2024~2025년 사이 OpenTelemetry가 사실상의 업계 표준으로 자리잡았다. AWS도 CloudWatch Agent의 OTLP(OpenTelemetry Protocol) 지원을 강화했으며, X-Ray의 OTLP 수집 엔드포인트를 공식 지원한다. 신규 프로젝트는 X-Ray SDK 대신 OpenTelemetry SDK 사용을 권장.
AI 기반 이상 탐지 (Anomaly Detection)
CloudWatch Anomaly Detection은 머신러닝으로 시계열 패턴을 학습해 정상 범위를 자동으로 계산한다. 절대적 임계값 대신 패턴 기반 알람으로 노이즈 알람을 줄이는 데 효과적이다.
활성화 경로: CloudWatch → Alarms → Create alarm→ "Anomaly detection" 선택 → 표준 편차 몇 배 벗어나면 알람할지 설정📖 더 보기: Observability 2025: Master Metrics, Logs, and Traces for Resilient Systems — Shift-Left Observability와 2025년 Observability 트렌드 전반 정리 (중급)