콘텐츠로 이동

Logs / Metrics / Traces

분류: Layer 6 - 운영 심화: 관측성 & 복원력 | 선수지식: CloudWatch Basics

Logs는 “무슨 일이 있었는지”, Metrics는 “상태가 어떤지”, Traces는 “요청이 어떤 경로로 흘렀는지”를 보여주는 관측 가능성(Observability)의 세 기둥이다.

서비스에 문제가 생겼을 때 “어디서 문제인지” 찾는 능력이 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의 출발점이다.

세 기둥이 어떻게 동작하는가 (전체 흐름)

섹션 제목: “세 기둥이 어떻게 동작하는가 (전체 흐름)”

비유:

  • 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 10

NestJS에서 구조화된 로그 구현 (Winston 사용):

logger.module.ts
// npm install winston nest-winston
import { 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 → CPUUtilization

Traces (추적)

하나의 요청이 여러 서비스를 거치는 전체 경로를 추적. 마이크로서비스 환경에서 중요.

Trace와 Span의 관계:

Trace (전체 요청 1건, 고유 Trace ID)
├── Span: API Gateway 처리 (2ms)
├── Span: OrderService 처리 (23ms)
│ ├── Span: DB 쿼리 - SELECT orders (18ms) ← 병목!
│ └── Span: 캐시 조회 (2ms)
└── Span: NotificationService 처리 (5ms)
총 소요: 30ms

AWS X-Ray 동작 방식:

  1. 첫 서비스(API Gateway 등)가 고유한 Trace ID를 HTTP 헤더(X-Amzn-Trace-Id)에 삽입
  2. 요청이 다음 서비스로 전달될 때 이 헤더가 그대로 전파 (Context Propagation)
  3. 각 서비스가 자신의 처리 시간(Span)을 X-Ray 데몬에 전송
  4. 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 기둥 비유

LogsMetricsTraces
비유일기장건강검진 수치택배 추적
질문”무슨 일이 있었어?""지금 상태가 어때?""요청이 어디를 거쳐갔어?”
특징상세하지만 양이 많음요약된 숫자, 추세 파악요청 단위 전체 경로

대표 도구

  • 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.ts
import "./tracing"; // ← 반드시 첫 번째 import
import { 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(특정) 순서로 진단
  • 장애 발생: Metrics로 이상 감지 → Logs로 원인 파악 → Traces로 병목 구간 특정
  • 성능 최적화: Metrics로 느린 구간 발견 → Traces로 상세 분석
  • 운영 대시보드: Metrics 기반으로 팀 서비스 상태 한눈에 확인
  • 장애 대응 시 “뭘 먼저 봐야 하는지” 판단 기준이 됨
  • 모니터링 개선 제안 시 “Logs/Metrics/Traces 중 뭐가 부족한지” 진단
  • CloudWatch만 쓰고 있다면, Traces 도입의 필요성 제안 가능
개념 A개념 B차이점
LogsMetricsLogs는 개별 이벤트 텍스트, Metrics는 집계된 숫자
MetricsTracesMetrics는 전체 시스템 요약, Traces는 개별 요청의 상세 경로
MonitoringObservabilityMonitoring은 “알려진 문제 감시”, Observability는 “모르는 문제도 진단 가능”
Structured LogUnstructured LogStructured(JSON)는 검색/분석 가능, Unstructured(텍스트)는 사람만 읽기 편함
OpenTelemetryX-Ray SDKOpenTelemetry는 벤더 중립 표준, X-Ray SDK는 AWS 전용 (OTel이 장기적으로 권장)

🔧 CloudWatch Log Insights에서 JSON 필드를 검색할 수 없다

섹션 제목: “🔧 CloudWatch Log Insights에서 JSON 필드를 검색할 수 없다”

증상: 로그가 JSON 형식인데 filter userId = "12345" 쿼리가 동작하지 않음 원인: CloudWatch Logs Insights는 JSON 로그를 자동 파싱하지만, 로그가 완전한 JSON 형식이 아니거나 앞에 텍스트가 붙으면 파싱 실패 해결:

  1. 로그 형식 확인: 전체 줄이 {...} JSON이어야 함. [INFO] {"userId":...} 처럼 앞에 텍스트가 있으면 파싱 안 됨
  2. 로그 출력 코드에서 console.log(JSON.stringify(obj)) 형태로 순수 JSON만 출력하도록 수정
  3. 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 데몬(사이드카 컨테이너)이 실행되지 않음 해결:

  1. ECS Task IAM Role → 정책 확인 → AWSXRayDaemonWriteAccess 정책 추가
  2. ECS Task Definition에 X-Ray 데몬 사이드카 컨테이너 추가:
    {
    "name": "xray-daemon",
    "image": "amazon/aws-xray-daemon",
    "portMappings": [{ "containerPort": 2000, "protocol": "udp" }]
    }
  3. Sampling 설정 확인 — 기본값은 초당 1건 + 5%만 샘플링하므로 로컬에선 거의 안 보일 수 있음

🔧 로그가 너무 많아서 CloudWatch 비용이 과다 발생한다

섹션 제목: “🔧 로그가 너무 많아서 CloudWatch 비용이 과다 발생한다”

증상: CloudWatch Logs 요금이 예상보다 수십 배 청구됨 원인: DEBUG/INFO 레벨 로그를 프로덕션에서도 그대로 출력하거나, 루프 내에서 매 반복마다 로그를 찍는 구조 해결:

  1. 환경별 로그 레벨 설정 — 프로덕션은 WARN 이상만 출력
    // NestJS - 환경변수로 로그 레벨 제어
    const app = await NestFactory.create(AppModule, {
    logger:
    process.env.NODE_ENV === "production"
    ? ["warn", "error"]
    : ["log", "debug", "error"],
    });
  2. CloudWatch Logs → Log Groups → Retention 설정 (기본값 무기한 → 30일로 변경)
    • 실무 팁: 비즈니스 중요도에 따른 계층화 보존 전략 적용
      • 프로덕션 에러 로그: 7일 즉시 접근 + 90일 보존
      • 일반 INFO 로그: 3일 보존
      • 스테이징 디버그 로그: 24시간만 보존
    • 이 전략으로 CloudWatch 로깅 비용 최대 30% 절감 효과 보고됨
  3. 고빈도 이벤트(헬스체크 등)는 로그 제외하거나 샘플링

🔧 OpenTelemetry 계측 후 앱 시작 속도가 느려졌다

섹션 제목: “🔧 OpenTelemetry 계측 후 앱 시작 속도가 느려졌다”

증상: OpenTelemetry SDK 추가 후 NestJS 앱 부팅 시간이 2~3초 늘어남 원인: getNodeAutoInstrumentations()가 모든 라이브러리를 계측 대상으로 로드하는 과정에서 초기화 오버헤드 발생 해결:

  1. 사용하지 않는 계측 라이브러리를 명시적으로 비활성화:
    getNodeAutoInstrumentations({
    "@opentelemetry/instrumentation-fs": { enabled: false }, // 파일 시스템 계측 제외
    "@opentelemetry/instrumentation-dns": { enabled: false },
    });
  2. tracing.tsmain.ts 최상단에서 import해야 함 — NestFactory.create() 이후에 호출하면 계측이 누락됨

🔧 Trace ID가 로그와 연결되지 않아 장애 시 추적이 어렵다

섹션 제목: “🔧 Trace ID가 로그와 연결되지 않아 장애 시 추적이 어렵다”

증상: X-Ray에서 특정 Trace를 발견했는데, 해당 요청의 CloudWatch 로그를 찾을 수 없음 원인: 로그에 Trace ID가 포함되어 있지 않아서 로그와 Trace를 상호 참조할 수 없음 해결:

  1. 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-..."}
  2. 이렇게 하면 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이 아닌 형태로 전송됨 해결:

  1. 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"}
  2. CloudWatch Log Insights에서 파싱 성공 여부 검증:

    # JSON 필드 기반 검색 (파싱 성공 동작)
    filter level = "ERROR"
    | fields @timestamp, level, message, userId
    | sort @timestamp desc
    | limit 20
  3. @message 필드에 프리픽스 텍스트가 포함된 경우 정규식으로 우회:

    # JSON 파싱이 되는 경우 텍스트 검색으로 대체
    filter @message like /ERROR/
  • Logs, Metrics, Traces의 차이를 각각 한 문장으로 설명할 수 있다
  • 장애 발생 시 어떤 순서로 이 세 가지를 확인하는지 설명할 수 있다
  • 팀 서비스에서 Logs/Metrics/Traces 중 뭘 쓰고 있고 뭐가 부족한지 안다
  • Structured Log(JSON)와 Unstructured Log의 차이와 장단점을 설명할 수 있다
  • OpenTelemetry가 X-Ray SDK와 어떻게 다른지 설명할 수 있다

OpenTelemetry, SLI/SLO/SLA, 에러 버짓, Golden Signals(Latency, Traffic, Errors, Saturation), Alerting 전략, Collector 패턴, Correlation ID

  • 팀 서비스의 모니터링 현황 파악 (Logs/Metrics/Traces 각각 뭘 쓰는지)
  • CloudWatch에서 Metrics 대시보드 확인 경로: CloudWatch → Dashboards 또는 Metrics → All metrics 확인: Golden Signals 4개(Latency, Traffic, Errors, Saturation)가 있는지 체크
  • 에러 발생 시 Logs → Metrics → Traces 순으로 추적해보기 Log Insights 쿼리:
    filter level = "ERROR"
    | stats count(*) as cnt by bin(5m)
    | sort @timestamp desc
    예상 출력: 5분 단위로 에러 발생 건수가 집계된 테이블
  • 부족한 부분이 있다면 개선 제안 초안 작성
  1. Logs(이벤트 기록), Metrics(상태 수치), Traces(요청 경로)는 Observability의 3 기둥이다
  2. 장애 대응: Metrics로 감지 → Logs로 원인 → Traces로 병목 특정
  3. 하나만으로는 부족하고, 세 가지를 조합해야 문제를 빠르게 진단할 수 있다
  4. 구조화된 로그(JSON)를 쓰면 검색과 분석이 훨씬 쉬워진다
  5. “우리 팀에 뭐가 부족한지” 아는 것 자체가 운영 개선의 출발점이다

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" 항목 찾아 설정

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 트렌드 전반 정리 (중급)