스프링부트 실무 가이드 6편: 모니터링과 옵저버빌리티

스프링부트 실무 가이드 6편: 모니터링과 옵저버빌리티


시리즈 네비게이션

이전현재다음
5편: DB 최적화6편: 모니터링7편: 로깅

서론

시스템이 아무리 잘 설계되어도 운영 중 문제가 발생한다. 중요한 건 얼마나 빨리 문제를 파악하고 해결하느냐 다. 이번 편에서는 옵저버빌리티의 핵심 개념과 Metrics 중심의 모니터링 구축 방법을 다룬다.

참고: 로그(Logs)에 대한 상세한 내용은 7편: 로깅 실무 가이드에서 다룬다.

6편에서 다루는 내용:

  • 옵저버빌리티의 3가지 축 (Metrics, Logs, Traces)
  • Prometheus와 Micrometer를 활용한 메트릭 수집
  • 커스텀 비즈니스 메트릭 구현
  • 헬스체크와 Kubernetes Probe
  • Grafana 대시보드와 알림 설정

목차


1. 옵저버빌리티(Observability)란?

1.1 모니터링 vs 옵저버빌리티

모니터링 (Monitoring):
"미리 정의한 것을 감시"
- CPU 사용률 > 80%면 알림
- 에러율 > 5%면 알림
- 알려진 문제만 감지 가능

옵저버빌리티 (Observability):
"시스템 내부 상태를 외부에서 이해"
- 왜 느려졌지? → 원인 추적 가능
- 어디서 병목이지? → 분석 가능
- 예상치 못한 문제도 파악 가능

모니터링은 “무엇을 볼지” 미리 정해야 합니다. 반면 옵저버빌리티는 시스템의 모든 상태를 수집해두고, 문제가 발생했을 때 “왜”를 추적할 수 있게 합니다.

1.2 옵저버빌리티의 3가지 축

┌─────────────────────────────────────────────────────────┐
│                    Observability                        │
│                                                         │
│   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │
│   │   Metrics   │  │    Logs     │  │   Traces    │   │
│   │   (메트릭)   │  │   (로그)    │  │  (트레이스)  │   │
│   └─────────────┘  └─────────────┘  └─────────────┘   │
│         │                │                │            │
│    "무엇이"         "무슨 일이"       "어디서"         │
│    "얼마나"         "일어났는가"      "어떤 경로로"    │
│                                                         │
└─────────────────────────────────────────────────────────┘
설명도구 예시
Metrics수치화된 시계열 데이터Prometheus, Datadog
Logs이벤트 기록ELK Stack, Loki
Traces요청 흐름 추적Jaeger, Zipkin

세 가지를 조합하면 장애 원인을 빠르게 파악할 수 있습니다:

  • Metrics: “에러율이 5% 초과했다” (문제 발생 감지)
  • Logs: “PaymentService에서 timeout 에러 발생” (상세 내용)
  • Traces: “외부 PG사 API 호출에서 5초 지연” (병목 지점)

1.3 실제 장애 대응 비교

장애 발생 시나리오:
오전 10:05 - 사용자 "결제가 안 돼요" 신고

옵저버빌리티 없이:
├── "로그 파일 어디있지?"
├── "어떤 서버에서 발생했지?"
├── "재현이 안 되는데..."
└── 3시간 후 원인 파악

옵저버빌리티 있으면:
├── 메트릭: 10:03부터 결제 API 지연 급증
├── 로그: PaymentService에서 timeout 에러
├── 트레이스: 외부 PG사 API 응답 5초
└── 15분 만에 원인 파악

2. Prometheus & Micrometer

2.1 Prometheus 아키텍처

┌─────────────────────────────────────────────────────────┐
│                    Prometheus 아키텍처                   │
│                                                         │
│   ┌─────────────┐     Pull      ┌─────────────┐        │
│   │   Spring    │ ◀──────────── │ Prometheus  │        │
│   │   Boot App  │  /actuator/   │   Server    │        │
│   │             │  prometheus   │             │        │
│   └─────────────┘               └──────┬──────┘        │
│                                        │                │
│                                        │ Query          │
│                                        ▼                │
│                                 ┌─────────────┐        │
│                                 │   Grafana   │        │
│                                 │  Dashboard  │        │
│                                 └─────────────┘        │
└─────────────────────────────────────────────────────────┘

특징:
- Pull 방식: Prometheus가 앱에서 메트릭을 가져감
- 시계열 DB: 시간에 따른 메트릭 변화 저장
- PromQL: 강력한 쿼리 언어

Pull 방식의 장점:

  • 애플리케이션은 메트릭을 노출만 하면 됨
  • Prometheus가 중앙에서 스크래핑 대상 관리
  • 애플리케이션의 독립성 유지

2.2 Micrometer의 역할

Micrometer = 메트릭의 SLF4J (추상화 계층)

┌─────────────────────────────────────────────────────────┐
│                     Application Code                     │
│                                                         │
│              meterRegistry.counter("orders").increment() │
│                              │                          │
│                              ▼                          │
│                    ┌─────────────────┐                  │
│                    │   Micrometer    │                  │
│                    │  (추상화 계층)   │                  │
│                    └────────┬────────┘                  │
│                             │                           │
│          ┌──────────────────┼──────────────────┐       │
│          ▼                  ▼                  ▼       │
│   ┌────────────┐    ┌────────────┐    ┌────────────┐  │
│   │ Prometheus │    │  Datadog   │    │ CloudWatch │  │
│   │  Registry  │    │  Registry  │    │  Registry  │  │
│   └────────────┘    └────────────┘    └────────────┘  │
└─────────────────────────────────────────────────────────┘

Micrometer를 사용하면:

  • 벤더 중립적 코드 작성 가능
  • 모니터링 시스템 변경 시 코드 수정 불필요
  • Spring Boot Actuator와 자동 통합

2.3 프로젝트 설정

// build.gradle.kts
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("io.micrometer:micrometer-registry-prometheus")
}
# application-prod.yml
management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus
  endpoint:
    health:
      show-details: when_authorized
  prometheus:
    metrics:
      export:
        enabled: true

2.4 메트릭 엔드포인트 확인

# Prometheus 메트릭 조회
curl http://localhost:8080/actuator/prometheus

# 출력 예시
# HELP jvm_memory_used_bytes Used JVM memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{area="heap",id="Eden Space"} 5.0331648E7
jvm_memory_used_bytes{area="heap",id="Survivor Space"} 6291456.0

# HELP http_server_requests_seconds HTTP request duration
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{method="GET",uri="/api/products"} 150
http_server_requests_seconds_sum{method="GET",uri="/api/products"} 12.5

Spring Boot Actuator가 자동으로 JVM, HTTP, 커넥션 풀 등의 기본 메트릭을 제공합니다.


3. 메트릭 유형

3.1 4가지 기본 메트릭 타입

┌─────────────────────────────────────────────────────────┐
│  1. Counter (카운터)                                     │
│     - 증가만 가능 (감소 불가)                            │
│     - 재시작 시 0으로 리셋                               │
│     - 예: 총 요청 수, 총 에러 수                         │
│                                                         │
│     0 → 1 → 2 → 3 → 4 → 5 → ...                        │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│  2. Gauge (게이지)                                      │
│     - 증가/감소 모두 가능                                │
│     - 현재 상태를 나타냄                                 │
│     - 예: 현재 메모리 사용량, 활성 스레드 수             │
│                                                         │
│     50 → 70 → 45 → 80 → 30 → ...                       │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│  3. Timer (타이머)                                      │
│     - 이벤트 지속 시간 + 발생 횟수                       │
│     - 예: API 응답 시간, 쿼리 실행 시간                  │
│                                                         │
│     count: 100, sum: 5.2s, max: 0.5s                   │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│  4. Histogram (히스토그램)                               │
│     - 값의 분포를 버킷으로 측정                          │
│     - 예: 응답 시간 분포 (0-100ms: 50%, 100-500ms: 40%) │
│                                                         │
│     bucket_0.1: 50, bucket_0.5: 90, bucket_1.0: 98     │
└─────────────────────────────────────────────────────────┘

3.2 언제 무엇을 사용하는가?

측정 대상메트릭 타입예시
누적 개수Counter주문 수, 에러 수
현재 상태Gauge메모리 사용량, 활성 연결 수
소요 시간TimerAPI 응답 시간
값 분포Histogram응답 시간 백분위

3.3 Counter vs Gauge 선택 기준

Counter를 써야 할 때:
- "지금까지 총 몇 건?" → 주문 수, 에러 수
- 절대 감소하지 않는 값
- rate() 함수로 "초당 증가율" 계산 가능

Gauge를 써야 할 때:
- "지금 현재 몇 개?" → 활성 연결 수, 큐 크기
- 증가/감소가 모두 가능한 값
- 순간 상태가 중요한 값

4. 커스텀 비즈니스 메트릭

4.1 OrderMetrics 구현

// MetricsConfig.kt
@Component
class OrderMetrics(private val meterRegistry: MeterRegistry) {

    // Counter: 주문 생성 수 (증가만)
    private val orderCreatedCounter: Counter = Counter.builder("marketplace.orders.created")
        .description("Total number of orders created")
        .register(meterRegistry)

    // Counter: 주문 취소 수
    private val orderCancelledCounter: Counter = Counter.builder("marketplace.orders.cancelled")
        .description("Total number of orders cancelled")
        .register(meterRegistry)

    // Counter: 주문 실패 수
    private val orderFailedCounter: Counter = Counter.builder("marketplace.orders.failed")
        .description("Total number of failed order attempts")
        .register(meterRegistry)

    // Timer: 주문 생성 소요 시간
    private val orderCreationTimer: Timer = Timer.builder("marketplace.orders.creation.time")
        .description("Time taken to create an order")
        .register(meterRegistry)

    // Gauge: 현재 활성 주문 수
    private val activeOrders: AtomicInteger = AtomicInteger(0)

    init {
        meterRegistry.gauge("marketplace.orders.active", activeOrders)
    }

    // 사용 메서드
    fun incrementOrderCreated() {
        orderCreatedCounter.increment()
        activeOrders.incrementAndGet()
    }

    fun incrementOrderCancelled() {
        orderCancelledCounter.increment()
        activeOrders.decrementAndGet()
    }

    fun incrementOrderFailed() {
        orderFailedCounter.increment()
    }

    // Timer로 소요 시간 측정
    fun <T> timeOrderCreation(block: () -> T): T {
        return orderCreationTimer.recordCallable(block)!!
    }
}

4.2 ProductMetrics 구현

@Component
class ProductMetrics(private val meterRegistry: MeterRegistry) {

    private val productCreatedCounter = Counter.builder("marketplace.products.created")
        .description("Total number of products created")
        .register(meterRegistry)

    private val productViewCounter = Counter.builder("marketplace.products.views")
        .description("Total number of product views")
        .register(meterRegistry)

    private val stockDecreasedCounter = Counter.builder("marketplace.products.stock.decreased")
        .description("Total number of stock decrease operations")
        .register(meterRegistry)

    private val insufficientStockCounter = Counter.builder("marketplace.products.stock.insufficient")
        .description("Total number of insufficient stock errors")
        .register(meterRegistry)

    fun incrementProductCreated() = productCreatedCounter.increment()
    fun incrementProductView() = productViewCounter.increment()
    fun incrementStockDecreased() = stockDecreasedCounter.increment()
    fun incrementInsufficientStock() = insufficientStockCounter.increment()
}

4.3 서비스에서 활용

// OrderService.kt에서 사용
@Service
class OrderService(
    private val orderMetrics: OrderMetrics
) {
    fun createOrder(request: CreateOrderRequest): OrderResponse {
        return orderMetrics.timeOrderCreation {
            try {
                // 주문 생성 로직
                val order = processOrder(request)
                orderMetrics.incrementOrderCreated()
                order
            } catch (e: Exception) {
                orderMetrics.incrementOrderFailed()
                throw e
            }
        }
    }
}

4.4 Prometheus에서 보이는 메트릭

# 주문 카운터
marketplace_orders_created_total 150
marketplace_orders_cancelled_total 12
marketplace_orders_failed_total 3

# 활성 주문 (Gauge)
marketplace_orders_active 138

# 주문 생성 시간 (Timer)
marketplace_orders_creation_time_seconds_count 150
marketplace_orders_creation_time_seconds_sum 45.2
marketplace_orders_creation_time_seconds_max 1.2

# 상품 메트릭
marketplace_products_views_total 5000
marketplace_products_stock_insufficient_total 23

5. 태그(Label)를 활용한 차원 분석

5.1 태그의 중요성

태그 없이:
marketplace_orders_created_total 150
→ 전체 주문 수만 알 수 있음

태그 있으면:
marketplace_orders_created_total{status="success",payment="card"} 100
marketplace_orders_created_total{status="success",payment="bank"} 40
marketplace_orders_created_total{status="failed",payment="card"} 10
→ 결제 수단별, 상태별 분석 가능

태그를 사용하면 하나의 메트릭으로 다양한 차원의 분석이 가능합니다.

5.2 태그 추가 방법

// 태그가 있는 Counter
private fun orderCounter(status: String, paymentType: String): Counter {
    return Counter.builder("marketplace.orders")
        .tag("status", status)
        .tag("payment_type", paymentType)
        .register(meterRegistry)
}

// 사용
fun recordOrder(paymentType: String, success: Boolean) {
    val status = if (success) "success" else "failed"
    orderCounter(status, paymentType).increment()
}

5.3 태그 사용 시 주의사항

❌ 잘못된 사용:
Counter.builder("orders")
    .tag("user_id", userId)     // 사용자별로 메트릭 폭발!
    .tag("order_id", orderId)   // 주문별로 메트릭 폭발!

✓ 올바른 사용:
Counter.builder("orders")
    .tag("status", "success")      // 제한된 값
    .tag("payment_type", "card")   // 제한된 값
    .tag("region", "seoul")        // 제한된 값

태그의 조합 수(Cardinality)가 너무 많으면 메모리 사용량이 급증합니다.

5.4 PromQL로 분석

# 전체 주문 수
sum(marketplace_orders_created_total)

# 결제 수단별 주문 수
sum by (payment_type) (marketplace_orders_created_total)

# 실패율
sum(marketplace_orders_created_total{status="failed"})
  / sum(marketplace_orders_created_total) * 100

# 최근 5분간 초당 주문 수
rate(marketplace_orders_created_total[5m])

6. 헬스체크 (Health Check)

6.1 헬스체크의 역할

"이 서비스가 정상인가?"를 판단하는 엔드포인트

GET /actuator/health

{
  "status": "UP",          ← 전체 상태
  "components": {
    "db": { "status": "UP" },          ← DB 연결 OK
    "redis": { "status": "UP" },       ← Redis OK
    "kafka": { "status": "UP" },       ← Kafka OK
    "diskSpace": { "status": "UP" }    ← 디스크 OK
  }
}

활용:
- Kubernetes: Liveness/Readiness Probe
- 로드밸런서: 정상 인스턴스만 트래픽 전달
- 모니터링: 장애 감지 및 알림

6.2 커스텀 HealthIndicator - Redis

// HealthIndicators.kt
@Component
@Profile("docker", "prod")
class RedisHealthIndicator(
    private val redisConnectionFactory: RedisConnectionFactory
) : HealthIndicator {

    override fun health(): Health {
        return try {
            val connection = redisConnectionFactory.connection
            val pong = connection.ping()  // PING → PONG
            connection.close()

            if (pong != null) {
                Health.up()
                    .withDetail("status", "Redis is available")
                    .withDetail("response", pong)
                    .build()
            } else {
                Health.down()
                    .withDetail("status", "Redis ping returned null")
                    .build()
            }
        } catch (e: Exception) {
            Health.down(e)
                .withDetail("status", "Redis is unavailable")
                .withDetail("error", e.message)
                .build()
        }
    }
}

6.3 커스텀 HealthIndicator - Kafka

@Component
@Profile("docker", "prod")
class KafkaHealthIndicator(
    private val kafkaTemplate: KafkaTemplate<String, Any>
) : HealthIndicator {

    override fun health(): Health {
        return try {
            kafkaTemplate.producerFactory  // Producer 초기화 확인
            Health.up()
                .withDetail("status", "Kafka producer is initialized")
                .build()
        } catch (e: Exception) {
            Health.down(e)
                .withDetail("status", "Kafka is unavailable")
                .build()
        }
    }
}

6.4 Kubernetes Probe 설정

# deployment.yaml
spec:
  containers:
    - name: marketplace-api
      livenessProbe:           # 살아있는지?
        httpGet:
          path: /actuator/health
          port: 8080
        initialDelaySeconds: 60
        periodSeconds: 10
        failureThreshold: 3    # 3번 실패하면 재시작

      readinessProbe:          # 트래픽 받을 준비 됐는지?
        httpGet:
          path: /actuator/health
          port: 8080
        initialDelaySeconds: 30
        periodSeconds: 5
        failureThreshold: 3    # 3번 실패하면 트래픽 제외

6.5 Liveness vs Readiness

Probe목적실패 시
Liveness컨테이너 생존 확인재시작
Readiness트래픽 수신 준비 확인서비스 제외 (재시작 안 함)
예시 시나리오:
┌────────────────────────────────────────────────────────┐
│ 1. Pod 시작                                            │
│    Liveness: 체크 안 함 (initialDelaySeconds 대기)     │
│    Readiness: FAIL → 트래픽 안 받음                    │
│                                                        │
│ 2. 앱 초기화 완료                                      │
│    Liveness: UP                                        │
│    Readiness: UP → 트래픽 받기 시작                    │
│                                                        │
│ 3. DB 연결 끊김                                        │
│    Liveness: UP (앱 자체는 살아있음)                   │
│    Readiness: DOWN → 트래픽 중단                       │
│                                                        │
│ 4. 앱 데드락                                           │
│    Liveness: FAIL → 컨테이너 재시작                    │
└────────────────────────────────────────────────────────┘

7. Grafana 대시보드

7.1 Grafana의 역할

Prometheus (데이터 저장) → Grafana (시각화)

┌─────────────────────────────────────────────────────────┐
│                     Grafana Dashboard                    │
│                                                         │
│  ┌─────────────────┐  ┌─────────────────┐              │
│  │   주문 수/분     │  │   응답 시간      │              │
│  │   ▄▄▄█▄▄▄▄█    │  │   ___/\___/\_   │              │
│  └─────────────────┘  └─────────────────┘              │
│                                                         │
│  ┌─────────────────┐  ┌─────────────────┐              │
│  │   에러율         │  │   활성 사용자    │              │
│  │      2.3%       │  │      1,234      │              │
│  └─────────────────┘  └─────────────────┘              │
│                                                         │
└─────────────────────────────────────────────────────────┘

7.2 데이터소스 설정

# grafana/provisioning/datasources/datasources.yml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    url: http://prometheus:9090
    access: proxy
    isDefault: true

7.3 유용한 PromQL 쿼리

RED Method (요청 기반 서비스):

# Rate: 초당 요청 수
rate(http_server_requests_seconds_count[5m])

# Errors: 에러율 (%)
sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
  / sum(rate(http_server_requests_seconds_count[5m])) * 100

# Duration: 평균 응답 시간
rate(http_server_requests_seconds_sum[5m])
  / rate(http_server_requests_seconds_count[5m])

# Duration: 95퍼센타일 응답 시간
histogram_quantile(0.95,
  rate(http_server_requests_seconds_bucket[5m]))

USE Method (리소스):

# Utilization: JVM 메모리 사용률
jvm_memory_used_bytes{area="heap"}
  / jvm_memory_max_bytes{area="heap"} * 100

# Saturation: HikariCP 대기 스레드
hikaricp_connections_pending

# Errors: DB 연결 타임아웃
hikaricp_connections_timeout_total

비즈니스 메트릭:

# 분당 주문 수
rate(marketplace_orders_created_total[1m]) * 60

# 주문 성공률
sum(marketplace_orders_created_total{status="success"})
  / sum(marketplace_orders_created_total) * 100

8. 알림 설정

8.1 알림 규칙 예시

# Prometheus alerting rules
groups:
  - name: marketplace-alerts
    rules:
      # 에러율 5% 초과
      - alert: HighErrorRate
        expr: |
          sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
          / sum(rate(http_server_requests_seconds_count[5m])) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value | humanizePercentage }}"

      # 응답 시간 2초 초과
      - alert: SlowResponseTime
        expr: |
          histogram_quantile(0.95,
            rate(http_server_requests_seconds_bucket[5m])) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Slow response time"

      # 서비스 다운
      - alert: ServiceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service {{ $labels.instance }} is down"

8.2 알림 설정 팁

  • for 절: 일시적 스파이크 무시 (5분간 지속 시에만 알림)
  • severity 레이블: 알림 우선순위 분류
  • annotations: Slack/PagerDuty 메시지에 포함될 내용

9. 프로젝트 파일 구조

marketplace/
├── marketplace-api/
│   ├── build.gradle.kts              # actuator, micrometer-prometheus
│   └── src/main/
│       ├── kotlin/.../config/
│       │   ├── MetricsConfig.kt      # OrderMetrics, ProductMetrics
│       │   └── HealthIndicators.kt   # Redis, Kafka 헬스체크
│       └── resources/
│           └── application-prod.yml  # Prometheus 엔드포인트 설정

├── k8s/monitoring/
│   └── prometheus.yaml               # Prometheus 배포 설정

├── grafana/
│   └── provisioning/
│       └── datasources/
│           └── datasources.yml       # Prometheus 데이터소스

└── docker-compose.yml                # Prometheus, Grafana 서비스

10. 면접 대비 Q&A

Q1. 옵저버빌리티의 3가지 축은?

목적예시
Metrics수치로 상태 파악CPU 80%, 에러율 2%
Logs이벤트 상세 기록에러 스택트레이스
Traces요청 흐름 추적A서비스 → B서비스 → DB

Q2. Counter와 Gauge의 차이는?

Counter: 증가만 가능, 누적 값
- 총 요청 수, 총 에러 수
- 재시작 시 0으로 리셋
- rate() 함수로 초당 증가율 계산

Gauge: 증가/감소 가능, 현재 상태
- 메모리 사용량, 활성 연결 수
- 순간 값을 나타냄

Q3. Prometheus의 Pull 방식 장점은?

Push 방식:
앱 → 모니터링 서버 전송
- 앱이 모니터링 서버 주소를 알아야 함
- 서버 장애 시 데이터 유실

Pull 방식 (Prometheus):
Prometheus → 앱에서 가져감
- 앱은 메트릭 노출만
- 중앙에서 스크래핑 대상 관리
- 앱 독립성 유지

Q4. Liveness와 Readiness Probe의 차이는?

Probe목적실패 시
Liveness컨테이너 생존 확인재시작
Readiness트래픽 수신 준비 확인서비스 제외

Liveness는 “죽었니?”를 확인하고, Readiness는 “준비됐니?”를 확인합니다.

Q5. rate()와 increase()의 차이는?

# rate(): 초당 평균 증가율
rate(http_requests_total[5m])  → 10.5 (초당 10.5개)

# increase(): 기간 내 총 증가량
increase(http_requests_total[5m])  → 3150 (5분간 3150개)

관계: increase() ≈ rate() × 시간(초)
  • rate(): 대시보드에서 초당 처리량 표시
  • increase(): 특정 기간 동안 총 발생 건수 확인

Q6. 어떤 메트릭을 모니터링해야 하나요?

RED Method (요청 기반 서비스):

  • Rate: 초당 요청 수
  • Errors: 에러율
  • Duration: 응답 시간

USE Method (리소스):

  • Utilization: 사용률 (CPU 80%)
  • Saturation: 포화도 (큐 대기)
  • Errors: 에러 수
서비스(API, 마이크로서비스) → RED Method
리소스(CPU, 메모리, DB) → USE Method

11. 핵심 정리

개념설명도구
Metrics수치 기반 시계열 데이터Prometheus + Micrometer
Logs상세 이벤트 기록ELK, Loki
Traces분산 요청 추적Jaeger, Zipkin
Counter누적 카운터 (증가만)요청 수, 에러 수
Gauge현재 상태 (증감 가능)메모리, 연결 수
Timer소요 시간 + 횟수API 응답 시간
HealthCheck서비스 상태 확인Kubernetes Probe

다음 편 예고

다음 7편에서는 옵저버빌리티의 두 번째 축인 로그(Logs) 를 다룬다.

  • SLF4J, Logback, Log4j2 기술 스택 비교
  • 구조화된 로그(JSON) 설정
  • MDC를 활용한 요청 추적
  • ELK Stack, Loki를 활용한 중앙 집중식 로그 관리
  • 실무에서 주의할 점 (민감정보 마스킹, 성능 등)

7편: 로깅 실무 가이드로 이동

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.