장애가 났을 때 어디부터 봐야 할까? 이 질문에 답하기 위한 핵심 개념인 로깅과 Observability에 대해 알아보자.
흔히들 모니터링(Monitoring)과 옵저버빌리티(Observabtility)의 개념을 혼동해서 쓰지만 다음과 같은 차이가 있다.
- 모니터링: "지금 문제가 있는가?"에 집중한다. 시스템의 상태를 감지하고 임계치를 넘으면 알람을 울린다.
- 옵저버빌리티: "왜 문제가 생겼는가?"를 파악하기 위한 도구다. 시스템의 외부 출력(로그, 지표, 추적)을 통해 내부 상태를 이해하고 원인을 추적한다.
장애 분석의 핵심은 무작정 원인을 추측하는 것이 아니라, 요청의 흐름을 따라가며 "어디까지 성공했고, 어디서 실패했는지 좁히는 것"이다.
🧩 로그가 왜 필요할까?
로그란?
개발할 때는 디버거가 있지만 운영에서는 로그가 곧 눈이다. 신입 개발자들이 흔히 범하는 실수는 에러가 났을 때 log.error("결제 실패")처럼 내용만 덜렁 남기는 것이다. 이런 로그가 수천 줄 쌓여 있다면 장애 상황에서 아무런 쓸모가 없다.
로그는 '많이' 찍는 것이 중요한 게 아니라, '찾을 수 있게' 찍는 것이 핵심이다. 좋은 로그는 검색하기 쉽고 다른 로그와 연결하기 쉬워야 하며, 다음 조건들을 만족해야 한다.
- 식별성: traceId, requestId를 통해 특정 요청을 검색할 수 있어야 한다.
- 맥락: userId, orderId 등으로 어떤 비즈니스 로직인지 파악할 수 있어야 한다.
- 출처: 어느 서비스(serviceName)에서 발생했는지 명확해야 한다.
- 원인 분류: 통계 및 알람 처리를 위해 errorCode가 있어야 한다.
- 시간 측정: 성능 분석을 위한 elapsedMs(소요 시간)가 포함되어야 한다.
- 예외 객체: Exception 객체를 반드시 마지막에 넘겨 스택 트레이스를 보존해야 한다.
try {
payment.process(order);
} catch (PaymentException e) {
log.error(
"Payment failed. orderId={}, userId={}, errorCode={}, elapsedMs={}",
order.getId(),
order.getUserId(),
e.getErrorCode(),
elapsed,
e
);
}
{
"level": "ERROR",
"service": "payment-service",
"traceId": "trc-20260515-001",
"orderId": "ORD-1004",
"userId": "U-77",
"message": "External payment API timeout",
"elapsedMs": 3200,
"errorCode": "PG_TIMEOUT"
}
로그 레벨의 올바른 사용
- ERROR: 사용자 요청이 실패하여 즉각적인 확인이 필요한 경우 (알람 필수)
- WARN: 실패는 아니지만 비정상적이거나 임계치에 근접한 경우
- INFO: 중요한 비즈니스 이벤트 (운영 환경에서는 단순 흐름 확인용 INFO는 끈다)
- DEBUG: 개발 시 흐름 확인용 (운영 환경 적용 금지)
🧩 API 요청 흐름으로 장애 좁혀가기
장애 분석에서 가장 중요한 것은 순서다. 무작정 코드를 뒤지는 것이 아니라 사용자 요청이 흘러가는 흐름대로 추적해야 한다. 백엔드에서 에러를 추적할 때는 다음 단계를 따라가며 원인을 좁힌다.
- 요청이 도착했는가: Access log
- Controller에 진입했는가: API 진입 로그
- 입력 검증에서 실패했는가: Validation error
- 비즈니스 로직에서 실패했는가: 도메인 예외
- DB 처리에서 실패했는가: SQL exception, timeout
- 외부 API 호출에서 실패했는가: HTTP 5xx, timeout
- 응답은 뭘로 나갔는가: Status code
결제 요청에서 타임아웃이 발생했다는 고객 문의가 들어왔을 때, 로그를 시간 순서로 펼치면 다음과 같이 보일 것이다.
[14:23:01] POST /orders 수신
[14:23:01] order-service: 주문 생성 시작 (orderId=ORD-1004)
[14:23:01] order-service: 주문 저장 성공 (120ms)
[14:23:01] order-service: payment-service 호출 시작
[14:23:04] payment-service: 외부 PG API 호출 시작
[14:23:07] payment-service: PG_TIMEOUT (3,200ms)
[14:23:07] order-service: 결제 실패 응답
흐름을 따라가면 원인을 쉽게 특정할 수 있다. 주문 저장은 성공했으니 DB 문제는 제외되고, 결제 서비스 호출은 성공했으니 네트워크 문제도 제외된다. 외부 PG API에서 3.2초 지연 후 타임아웃이 발생한 것이 진짜 원인이다.
🧩 옵저버빌리티의 3요소
Metrics (지표) : "지금 이상한가?"
시스템의 현재 상태를 수치로 보여준다. 트래픽, 에러율, 응답시간, CPU 사용률 등을 파악한다. 구글이 권장하는 4대 황금 지표(Four Golden Signals)는 Latency(응답시간), Traffic(요청량), Errors(에러율), Saturation(포화도)이다.
이때 응답시간은 '평균'을 보면 안 된다. 평균은 소수의 극단적으로 느린 요청을 숨겨버린다. 대신 사용자 경험의 경계선을 보여주는 백분위수(p50, p95, p99)를 핵심 지표로 보아야 한다.
Logs (로그) : "정확히 무슨 일이 있었나?"
메트릭으로 이상을 감지했다면, 구조화된 로그를 통해 그 순간의 정확한 사건 기록을 확인한다. 앞서 언급한 식별자와 맥락 정보가 모두 들어있어야 원인을 특정할 수 있다.
Traces (추적) : "어디서 느렸나?"
요청 하나가 여러 단계를 거칠 때, 어느 구간에서 시간이 오래 걸렸는지 시각화하여 보여준다. 외부 통신 지연인지, DB 병목인지 직관적으로 파악할 수 있다.
🧩 Trace ID의 중요성
모놀리식 구조와 달리 MSA에서는 하나의 요청이 여러 서버를 거치며 파편화된 로그를 남긴다. 이 흩어진 로그들이 '같은 사용자의 같은 요청'에서 비롯되었음을 증명하는 것이 Trace ID다.
- 처음 요청이 들어올 때 고유한 Trace ID가 발급된다.
- 서버 간 통신 시 HTTP 헤더(W3C 표준 traceparent 등)에 이 ID를 담아 전달한다.
- Spring Boot 환경에서는 MDC(Mapped Diagnostic Context)를 활용해 현재 스레드의 모든 로그에 Trace ID를 자동으로 부여할 수 있다. 단, 비동기 작업 시에는 스레드가 변경되므로 Trace ID가 유실되지 않도록 전파 처리가 필요하다.
- 전체 요청을 묶는 것이 Trace ID라면, 그 안의 각 호출 단계를 구분하는 것은 Span ID다.
🧩 로그 작성 시 절대 주의사항
비밀번호, 주민등록번호, 전체 카드번호, 인증 토큰(JWT 등), 계좌번호 같은 민감 정보는 절대 로그에 평문으로 남겨서는 안 된다. 편의를 위해 requestDto를 통째로 로그에 찍는 패턴은 매우 위험하다. 필요한 필드만 골라서 남기거나, 반드시 일부 정보가 필요하다면 마스킹 처리(1234-****-****-3456)를 해야 한다.
🧩 관찰성 도구 생태계
운영 환경의 데이터 파이프라인은 대체로 다음 구조를 따른다.
- 수집기: OpenTelemetry Collector, Fluent Bit 등이 애플리케이션 데이터를 받아 변환하고 전송한다. OpenTelemetry는 관찰성 데이터 수집의 사실상 업계 표준이다.
- 저장소: 메트릭은 Prometheus, 로그는 ELK나 Loki, 트레이스는 Jaeger, Zipkin, Tempo에 주로 저장한다.
- 시각화: Grafana, Kibana 등으로 사람이 볼 수 있는 대시보드를 구성한다.
- 통합 SaaS / 클라우드 도구: Datadog, New Relic, AWS CloudWatch 등은 위 과정을 한 번에 해결해 준다.
🧩 장애 대응 6단계 프로세스
장애가 발생했을 때는 다음 순서로 좁혀가야 한다.
- 요청 도착 확인: Access log를 통해 API Gateway나 네트워크 구간에서 막히지 않았는지 확인한다.
- 규모 파악: Metrics를 보고 특정 API 문제인지 전체 장애인지, 에러율은 어느 정도인지 판단한다.
- 문제 요청 식별: 고객 문의 정보나 특정 로그를 통해 문제가 발생한 Trace ID를 확보한다.
- 실패 구간 좁히기: 어디까지 코드가 실행되었고 어디서 끊겼는지 흐름을 거슬러 올라가지 말고 '앞에서부터' 확인한다.
- 로그 연결: Trace ID로 여러 서비스에 흩어진 로그를 한 줄로 모아본다.
- 원인 도출 및 조치: 외부 API, DB, 네트워크, 코드 에러 등 원인을 좁히고 적절한 조치를 취한다.
'내일배움캠프' 카테고리의 다른 글
| [내일배움캠프] 모니터링 시스템 (0) | 2026.06.02 |
|---|---|
| [내일배움캠프] Saga 패턴 (0) | 2026.05.14 |
| [내일배움캠프] 이벤트 소싱과 CQRS, RabbitMQ와 Kafka (0) | 2026.05.13 |
| [내일배움캠프] 대규모 시스템에서의 DB 최적화와 분산 트랜잭션 (0) | 2026.05.12 |
| [내일배움캠프] Redis 심화 (0) | 2026.05.12 |