본문 바로가기
클라우드/opentelemetry

🚦 OpenTelemetry Span 상태 완벽 정복: Unset, Ok, Error 파헤치기

by gasbugs 2025. 10. 18.

안녕하세요! 최신 분산 시스템 환경에서 서비스의 동작을 추적하고 문제를 해결하는 것은 정말 중요한 과제죠. 이때 우리를 도와주는 강력한 도구가 바로 OpenTelemetry(OTel)입니다.

 

OpenTelemetry는 수많은 마이크로서비스에서 발생하는 요청의 흐름(Trace)을 시각화하고 분석할 수 있게 해주는데요, 이 흐름을 구성하는 가장 작은 작업 단위를 스팬(Span)이라고 부릅니다. 🗺️

 

오늘은 이 스팬의 "상태"를 나타내는 SpanStatus에 대해 깊이 있게 알아보려고 합니다. 단순히 "성공"과 "실패"로만 나누기에는 조금 더 디테일한 이야기가 숨어있답니다. 이 세 가지 상태 코드의 차이점을 명확히 이해하면, 시스템을 훨씬 더 정교하게 모니터링할 수 있게 될 거예요


🧐 전체적인 그림: 스팬과 그 상태는 왜 중요할까?

애플리케이션에서 특정 기능(예: '상품 주문 처리')이 실행되면, 이 과정은 하나의 거대한 트레이스(Trace)로 기록됩니다. 그리고 이 트레이스는 여러 개의 작은 작업 단위인 스팬(Span)으로 구성되죠.

  • 스팬 1: API 요청 수신
  • 스팬 2: 사용자 인증 확인
  • 스팬 3: 데이터베이스에서 상품 정보 조회
  • 스팬 4: 결제 서비스 호출 💳
  • 스팬 5: 주문 완료 응답 전송

여기서 각 스팬이 성공적으로 끝났는지, 아니면 중간에 문제가 생겼는지 알아야 전체 '상품 주문 처리' 흐름의 병목이나 오류 지점을 정확히 찾아낼 수 있습니다. 바로 이때 SpanStatus가 핵심적인 역할을 합니다.

 

SpanStatus는 각 작업 단위의 결과를 알려주는 신호등과 같습니다. 이제 신호등의 세 가지 색깔을 하나씩 살펴볼까요?


1️⃣ Unset: 기본 상태 (🤔 회색 신호등)

Unset은 스팬이 생성될 때의 기본값입니다. 어떤 상태도 명시적으로 설정되지 않았다는 의미죠.

  • 의미: "일단 별다른 오류는 없었어. 하지만 내가 성공했다고 보장하는 건 아니야."
  • 특징: 대부분의 OpenTelemetry 백엔드(Jaeger, Zipkin 등)에서는 Unset 상태를 성공으로 간주하고 처리합니다. 오류가 아니면 성공으로 보는 것이죠.
  • 언제 사용되나?: 스팬이 시작되고, 아무런 오류 없이 정상적으로 종료되었지만 개발자가 굳이 Ok 상태를 설정하지 않은 경우 이 상태로 남게 됩니다.

간단한 정보 조회처럼 실패해도 치명적이지 않거나, 성공 여부를 명시적으로 기록할 필요가 없는 작업에 적합합니다.

 

2️⃣ Ok: 명시적인 성공 (✅ 녹색 신호등)

Ok는 작업이 명시적으로 성공했음을 나타냅니다. 이것이 바로 Unset과의 가장 큰 차이점입니다.

  • 의미: "이 작업은 내가 의도한 대로 완벽하게 성공했어!"
  • 특징: 개발자가 코드 내에서 span.set_status(StatusCode.OK)와 같이 의도를 가지고 설정해야 합니다.
  • 왜 중요할까?: 결제 처리, 회원 가입, 중요 데이터 저장 등 반드시 성공 여부를 확인해야 하는 비즈니스 로직에서 매우 유용합니다. 나중에 트레이스를 분석할 때, '오류가 없었던' 스팬과 '명확히 성공한' 스팬을 구분하여 볼 수 있기 때문에 데이터의 신뢰성이 올라갑니다.

예를 들어, "지난 한 시간 동안 결제 성공률은?"과 같은 질문에 정확히 답하려면 Ok 상태로 기록된 스팬을 집계해야 합니다.

 

3️⃣ Error: 명시적인 오류 (❌ 빨간색 신호등)

Error는 이름에서 알 수 있듯이, 작업 처리 중 오류가 발생했음을 나타냅니다.

  • 의미: "문제가 발생했어! 확인해줘!"
  • 특징: 보통 try-except 구문에서 예외(exception)가 발생했을 때 설정합니다. 단순히 상태만 Error로 바꾸는 것뿐만 아니라, 오류 메시지나 스택 트레이스를 스팬에 함께 기록(span.record_exception(e))하여 디버깅을 돕는 것이 일반적입니다.
  • 언제 사용되나?: 네트워크 타임아웃, 데이터베이스 연결 실패, 잘못된 파라미터로 인한 예외 등 모든 예상치 못한 문제 상황에서 사용됩니다.

💻 코드로 이해하는 SpanStatus의 흐름

말로만 설명하면 감이 잘 안 올 수 있죠. Python OpenTelemetry SDK를 사용한 간단한 예시 코드를 통해 전체 프로세스를 이해해 보겠습니다.

from opentelemetry import trace
from opentelemetry.trace import StatusCode

# OTel Tracer 설정 (이 부분은 미리 설정되어 있다고 가정합니다)
tracer = trace.get_tracer(__name__)

def process_payment(amount: int, card_number: str):
    # 'process_payment' 라는 이름으로 새로운 스팬을 시작합니다.
    # 스팬이 시작되는 이 시점의 기본 상태는 'Unset' 입니다.
    with tracer.start_as_current_span("process_payment") as span:
        try:
            span.set_attribute("payment.amount", amount)
            span.add_event("Payment processing started")

            if not card_number.startswith("4"):
                # 유효하지 않은 카드 번호인 경우 예외 발생
                raise ValueError("Invalid card number")

            # --- 여기에 실제 결제 로직이 들어갑니다 ---
            print(f"Processing payment of {amount}...")
            # --- 결제 성공 ---

            # ⭐️ 작업이 성공적으로 완료되었음을 명시적으로 기록합니다.
            # 이 코드가 실행되면 스팬 상태는 'Unset' -> 'Ok'로 변경됩니다.
            span.set_status(StatusCode.OK)
            span.add_event("Payment successful!")

            return {"status": "success"}

        except Exception as e:
            # 🚨 예외가 발생하면 오류 상태로 설정합니다.
            # 스팬 상태는 'Unset' -> 'Error'로 변경됩니다.
            error_message = f"Failed to process payment: {e}"
            span.set_status(StatusCode.ERROR, error_message)

            # 어떤 예외가 발생했는지 스팬에 기록하면 나중에 분석하기 좋습니다.
            span.record_exception(e)

            return {"status": "failed", "error": error_message}

# 함수 호출 예시
process_payment(100, "4123-...") # 성공 케이스
process_payment(50, "5432-...")  # 실패 케이스

📊 결과 스팬 Raw Data 비교

위 코드가 실행된 후 수집된 스팬 데이터는 다음과 같은 형태를 띨 것입니다. (JSON 형식으로 단순화)

1. 성공 케이스 (Ok)

{
  "name": "process_payment",
  "context": { ... },
  "attributes": {
    "payment.amount": 100
  },
  "events": [
    {"name": "Payment processing started", ...},
    {"name": "Payment successful!", ...}
  ],
  "status": {
    "status_code": "OK"
  },
  ...
}

2. 실패 케이스 (Error)

{
  "name": "process_payment",
  "context": { ... },
  "attributes": {
    "payment.amount": 50
  },
  "events": [
    {"name": "Payment processing started", ...},
    {
      "name": "exception",
      "attributes": {
        "exception.type": "ValueError",
        "exception.message": "Invalid card number",
        ...
      }
    }
  ],
  "status": {
    "status_code": "ERROR",
    "description": "Failed to process payment: Invalid card number"
  },
  ...
}

 

데이터를 보면 status_code가 어떻게 명확하게 OK와 ERROR로 나뉘는지, 그리고 오류 발생 시 상세 설명(description)과 예외 정보(exception 이벤트)가 어떻게 추가되는지 확인할 수 있습니다.


✨ 결론: 신호등을 올바르게 사용하자!

SpanStatus의 세 가지 상태를 정리해 보겠습니다.

  • Unset 🤔: 기본값. 오류가 없으면 성공으로 간주되지만, 보장된 성공은 아님.
  • Ok ✅: 명시적인 성공. "이 작업은 확실히 성공했다"는 강력한 신호.
  • Error ❌: 명시적인 실패. 문제 발생을 알리고 디버깅에 필요한 정보를 담는 상태.

단순히 오류만 잡는 것을 넘어, 중요한 작업의 성공을 Ok로 명시적으로 기록하는 습관을 들이는 것이 좋습니다. 이렇게 하면 시스템의 동작을 훨씬 더 명확하고 신뢰성 있게 파악할 수 있으며, 장애가 발생했을 때 문제의 원인을 빠르고 정확하게 찾아내는 강력한 무기가 될 것입니다.