로깅(Observability) 완전 정리장애가 나면 어디부터 봐야 할까?

Backend · Observability · Logging

로깅(Observability) 완전 정리
장애가 나면 어디부터 봐야 할까?

운영 환경에서는 디버거를 붙일 수 없습니다. 결국 우리에게 남는 건 로그, 메트릭, 트레이스입니다. 왜 로깅이 필요한지, 언제 무엇을 봐야 하는지, 실무에서 어떻게 써야 하는지를 백엔드 개발자 관점에서 한 번에 정리합니다.

#Observability#Logging#Metrics#TraceId#SpringBoot#MSA

왜 Observability가 필요한가

금요일 오후 6시, 퇴근 직전 슬랙에 메시지가 옵니다.

고객센터에서 결제가 안 된다고 문의가 들어왔어요. 확인 부탁드려요.

이때 무작정 코드를 뒤지는 것부터 시작하면 시간이 오래 걸립니다. 운영 환경에서는 브레이크포인트를 걸고 한 줄씩 따라갈 수 없기 때문입니다. 대신 우리는 시스템이 남긴 흔적을 따라가야 합니다.

LOGS

무슨 일이 있었는가

에러 메시지, 주문 번호, 사용자 ID, 처리 시간처럼 사건의 맥락을 보여줍니다.

METRICS

지금 이상한가

에러율, 응답시간, 트래픽, CPU처럼 숫자로 장애 징후를 빠르게 감지합니다.

TRACES

어디서 느렸는가

한 요청이 어떤 서비스와 구간을 지나며 어디서 병목이 났는지 보여줍니다.

결국 Observability의 핵심은 단순합니다. 어디까지 성공했고, 어디서 실패했는지 빠르게 좁히는 것입니다.

Monitoring vs Observability

면접에서도 자주 나오는 질문이죠. 많은 분들이 둘을 비슷하게 생각하지만, 실무에서는 분명한 차이가 있습니다.

구분 Monitoring Observability
핵심 질문 지금 문제가 있는가? 왜 문제가 생겼는가?
주요 데이터 메트릭, 알람 메트릭, 로그, 트레이스
대표 예시 에러율 5% 초과 알람 PG timeout이 어디서 났는지 추적
목적 감지 원인 분석

🔔 Monitoring

자동차 계기판의 경고등과 비슷합니다. "문제가 있다"는 사실은 알려주지만, 왜 생겼는지는 알려주지 못합니다.

🔬 Observability

정비사가 엔진, 팬, 오일 상태를 보며 원인을 좁혀가는 과정과 비슷합니다. 문제의 내부 상태를 이해할 수 있게 해주는 능력입니다.

Metrics / Logs / Traces는 언제 써야 할까?

실무에서는 세 가지를 경쟁 관계로 보지 않습니다. 각자 답하는 질문이 다르기 때문에 함께 써야 합니다.

도구 한마디로 답하는 질문 언제 가장 유용한가
Metrics 숫자 얼마나 느린가? 얼마나 실패하는가? 장애 징후를 가장 먼저 볼 때
Logs 사건 기록 그 순간 무슨 일이 있었나? 실패 요청의 맥락과 에러 원인을 확인할 때
Traces 요청 경로 어느 구간에서 오래 걸렸나? MSA, 외부 API 호출, 병목 구간 분석 시
Metrics를 볼 때
  • 에러율이 갑자기 튀었는지 보고 싶을 때
  • 응답시간이 평소보다 느려졌는지 확인할 때
  • CPU, 메모리, DB 커넥션이 포화 상태인지 볼 때
Logs를 볼 때
  • 어떤 주문, 어떤 사용자에서 실패했는지 확인할 때
  • 실제 에러 메시지와 에러 코드를 보고 싶을 때
  • 스택 트레이스로 코드 레벨 원인을 좁힐 때
Traces를 볼 때
  • Order → Payment → PG 중 어디가 느린지 알고 싶을 때
  • MSA에서 서비스 간 호출 흐름을 한눈에 보고 싶을 때
  • 외부 API, Kafka, 비동기 구간 병목을 분석할 때

장애 분석은 위에서 아래로 내려갑니다

Metrics로 이상 징후 감지 → Trace로 느린 구간 확인 → Logs로 정확한 원인 분석

평균보다 p95, p99를 보는 이유

평균 응답시간은 종종 거짓말을 합니다. 95명은 200ms에 응답받고, 5명은 10초를 기다려도 평균은 "그럭저럭"처럼 보일 수 있습니다. 그래서 실무에서는 p95, p99 같은 백분위수를 더 중요하게 봅니다.

지표 의미 실무 해석
p50 50% 사용자가 이 안에 응답받음 보통 사용자 경험
p95 95% 사용자가 이 안에 응답받음 느린 사용자 5%의 경계
p99 99% 사용자가 이 안에 응답받음 최악에 가까운 사용자 경험

좋은 로그는 어떻게 남겨야 할까?

로그는 많이 찍는 게 핵심이 아닙니다. 검색 가능하게, 맥락 있게, 원인을 좁힐 수 있게 남겨야 합니다.

❌ 나쁜 로그

try {
  payment.process(order);
} catch (Exception e) {
  log.error("결제 실패");
}
  • 어떤 주문인지 모릅니다
  • 어떤 사용자인지 모릅니다
  • 왜 실패했는지 모릅니다
  • 얼마나 걸렸는지 모릅니다

✅ 좋은 로그

try {
  payment.process(order);
} catch (PaymentException e) {
  log.error(
    "Payment failed. orderId={}, userId={}, errorCode={}, elapsedMs={}",
    order.getId(), order.getUserId(),
    e.getErrorCode(), elapsed, e
  );
}
  • 업무 맥락(orderId, userId)이 있습니다
  • 원인 분류(errorCode)가 가능합니다
  • 성능 분석(elapsedMs)이 가능합니다

구조화 로그를 쓰면 더 좋아집니다

{
  "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"
}

좋은 로그의 5가지 조건

조건 왜 필요한가 예시 필드
식별 가능 검색해서 원하는 요청을 찾아야 함 traceId, requestId
맥락 보유 어떤 업무에서 발생했는지 알아야 함 userId, orderId
출처 명확 어느 서비스의 문제인지 알아야 함 serviceName
원인 분류 알람/통계/패턴 분석에 필요 errorCode
시간 측정 느린 요청과 성능 저하를 잡아야 함 elapsedMs

로그 레벨 구분

레벨 언제 쓰는가 예시
ERROR 사용자 요청이 실제로 실패했을 때 결제 실패, DB 연결 실패
WARN 실패는 아니지만 비정상 패턴일 때 재시도 후 성공, 임계치 근접
INFO 중요한 비즈니스 이벤트를 남길 때 주문 생성, 결제 완료
DEBUG 개발/테스트 환경에서 내부 흐름을 볼 때 변수 값, 상세 분기 로직

장애가 났을 때 어디부터 봐야 할까?

실무에서 가장 중요한 건 순서입니다. 요청 흐름을 따라 어디까지 성공했는지 확인해야 합니다.

1

요청이 들어왔는지 확인

access log, API Gateway, Load Balancer 로그를 봅니다.

2

규모 파악

Metrics에서 에러율, p95 응답시간, 특정 인스턴스/API 문제인지 먼저 봅니다.

3

문제 요청 식별

traceId, requestId, orderId, userId 중 하나를 확보합니다.

4

흐름 따라 좁히기

Controller → Service → DB → 외부 API → 응답 순서대로 확인합니다.

5

MSA라면 서비스 간 로그 연결

traceId로 order-service, payment-service 로그를 한 화면에서 이어 봅니다.

6

원인 후보 정리

코드, DB, 외부 API, 네트워크, 메시징, 리소스 포화 중 원인을 확정합니다.

결제 실패 시나리오 예시

[14:23:01] POST /orders 수신
[14:23:01] order-service:   주문 생성 시작 (orderId=ORD-1004)
[14:23:01] order-service:   주문 저장 성공 (120ms)
[14:23:04] payment-service: 외부 PG API 호출 시작
[14:23:07] payment-service: PG_TIMEOUT (3,200ms)
[14:23:07] order-service:   결제 실패 응답
주문 저장은 성공했고, 결제 서비스 호출도 성공했지만, 외부 PG API에서 3.2초 지연 후 timeout이 발생했습니다. 이 장애의 핵심 원인은 외부 결제사 응답 지연입니다.

MSA에서 Trace ID가 왜 중요한가?

모놀리식에서는 한 서버의 로그만 보면 됐지만, MSA에서는 같은 요청의 로그가 여러 서비스에 흩어집니다.

모놀리식

Client → Backend → DB

로그가 한곳에 모여 있어 비교적 추적이 쉽습니다.

MSA

Client → Order → Payment → Coupon → Notification

같은 요청의 로그가 서비스별로 분산되어 공통 식별자가 필수입니다.

이때 필요한 것이 Trace ID입니다. 사용자 요청 하나가 시작될 때 고유 ID를 만들고, 그 요청이 거치는 모든 서비스 로그에 같은 값을 남깁니다.

order-service        | traceId=trc-001 | 주문 생성
payment-service      | traceId=trc-001 | 결제 실패
coupon-service       | traceId=trc-001 | 쿠폰 사용
notification-service | traceId=trc-001 | 알림 발송 실패
같은 요청이라면, 다음 서비스 호출 시에도 같은 traceId를 헤더에 실어 보내야 합니다.

표준 관점에서는 W3C Trace Context의 traceparent 헤더를 많이 사용합니다. 표준을 따르면 도구 간 연동이 쉬워집니다.

Spring Boot에서 MDC로 traceId 자동 부착하기

매번 로그를 찍을 때마다 traceId를 직접 넘기면 코드가 지저분해집니다. 이때 사용하는 것이 MDC (Mapped Diagnostic Context)입니다.

왜 쓰나

요청 단위 공통 값 자동 출력

traceId, requestId 등을 스레드 컨텍스트에 넣어두고 로그 패턴에서 자동으로 출력합니다.

언제 쓰나

MSA 로그 연결이 필요할 때

Spring Boot API 서버에서 요청 단위 추적이 필요할 때 매우 유용합니다.

// 1. 요청 입구에서 traceId 생성 또는 이어받기
MDC.put("traceId", UUID.randomUUID().toString());

// 2. 이후 로그에는 자동으로 traceId 부착
log.info("Order created. orderId={}", order.getId());

// 3. 요청 종료 시 반드시 정리
MDC.clear();

Filter에서 처리하는 예시

@Component
public class TraceIdFilter extends OncePerRequestFilter {
    private static final String TRACE_HEADER = "X-Trace-Id";

    @Override
    protected void doFilterInternal(
        HttpServletRequest req, HttpServletResponse res, FilterChain chain
    ) throws ServletException, IOException {
        try {
            String traceId = req.getHeader(TRACE_HEADER);
            if (traceId == null || traceId.isBlank()) {
                traceId = UUID.randomUUID().toString();
            }
            MDC.put("traceId", traceId);
            res.setHeader(TRACE_HEADER, traceId);
            chain.doFilter(req, res);
        } finally {
            MDC.clear();
        }
    }
}
⚠️ 주의: 비동기 작업에서는 MDC가 자동 전파되지 않을 수 있습니다. @Async, CompletableFuture, Kafka Consumer 같은 구간에서는 traceId 전파 전략을 별도로 고려해야 합니다.

traceId만으로 부족할 때: Span

Trace ID: trc-001
├─ 주문 조회          20ms
├─ 쿠폰 검증          80ms
├─ 결제 요청        3200ms  ← 병목
│  ├─ 카드사 통신   3150ms
│  └─ 결과 저장       30ms
└─ 알림 발송          90ms

Kafka도 관찰 대상입니다

  • 메시지 발행 시 traceId를 헤더에 담아 전파
  • Consumer 로그에도 같은 traceId 기록
  • lag 증가, 재처리 횟수, DLQ 적재량도 함께 모니터링

절대 로그에 남기면 안 되는 정보

특히 개인정보와 인증 정보는 절대 평문으로 남기면 안 됩니다.

절대 금지
  • 비밀번호
  • 주민등록번호
  • 카드번호 전체 / CVC / CVV
  • JWT, Access Token, Refresh Token
  • 세션 ID
  • 계좌번호, 의료 정보
흔한 실수
log.info("Request received: {}", requestDto); // ← 절대 금지

요청 객체를 통째로 찍으면 password, token, cardNumber 같은 민감 정보가 함께 남을 수 있습니다.

안전한 방식: 필요한 값만 선택적으로 기록

log.info("Payment request received. traceId={}, orderId={}, userId={}, amount={}",
    traceId, orderId, maskedUserId, order.getAmount());

마스킹 예시

// 원본: 1234-5678-9012-3456  →  결과: 1234-****-****-3456
String masked = cardNumber.substring(0, 4) + "-****-****-" + cardNumber.substring(15);
로그는 디버깅 도구이기도 하지만, 보안 관점에서는 민감한 데이터가 가장 쉽게 새는 통로가 되기도 합니다.

실무에서는 어떤 도구를 조합할까?

중요한 건 도구 이름을 외우는 게 아니라, 각 도구가 어떤 역할을 맡는지 이해하는 것입니다.

영역 대표 도구 역할
계측 표준 OpenTelemetry 로그, 메트릭, 트레이스를 표준 방식으로 생성/전달
메트릭 저장소 Prometheus 시계열 지표 저장
로그 저장소 Loki, ELK 로그 검색/집계/분석
트레이스 저장소 Jaeger, Zipkin, Tempo 분산 추적 및 병목 확인
시각화 Grafana, Kibana 대시보드, 검색, 알람
에러 트래킹 Sentry 에러 그룹화, 스택 추적, 영향 범위 확인
클라우드 기본 도구 CloudWatch, Azure Monitor 기본 로그/메트릭/알람 운영

회사 규모별로 자주 보이는 조합

초기 스타트업Sentry + CloudWatch 또는 Datadog
중간 규모 회사Sentry + 통합 SaaS (Datadog, New Relic)
Kubernetes 기반Prometheus + Grafana + Loki + Tempo
한국 대기업/금융권Pinpoint + 자체 로그 시스템

실무 체크리스트

운영 환경에서 바로 써먹을 수 있는 기준만 짧게 정리합니다.

로그 체크리스트
  • traceId / requestId가 있는가
  • userId / orderId 같은 업무 맥락이 있는가
  • serviceName, errorCode가 있는가
  • elapsedMs를 남기고 있는가
  • Exception 객체를 마지막 인자로 넘기고 있는가
  • 민감 정보를 마스킹하거나 제외했는가
장애 대응 체크리스트
  • 요청이 실제로 들어왔는지 먼저 확인
  • Metrics로 범위와 규모를 파악
  • 문제 요청 하나를 선정
  • traceId로 관련 로그 연결
  • 어디까지 성공했는지 순서대로 확인
  • DB / 외부 API / 네트워크 / 코드 중 원인 확정
좋은 Observability의 목표는 로그를 예쁘게 쌓는 것이 아니라, 새벽 2시에도 원인을 빠르게 좁힐 수 있게 만드는 것입니다.
 

마무리

운영 환경에서 로그는 단순한 출력문이 아닙니다. 메트릭은 문제가 있다고 알려주고, 트레이스는 어디가 느린지 보여주며, 로그는 정확히 무슨 일이 있었는지 말해줍니다.

백엔드 개발자가 성장할수록 중요한 건 기능 구현 속도만이 아닙니다. 문제가 생겼을 때 빠르게 복구할 수 있는 시스템을 만드는 능력, 그게 바로 Observability입니다.

#로그는많이가아니라찾기쉽게 #Metrics먼저Trace다음Logs마지막 #MSA에서는TraceId필수 #민감정보는절대로그금지