스프링부트 실무 가이드 6편: 모니터링과 옵저버빌리티
시리즈 네비게이션
| 이전 | 현재 | 다음 |
|---|---|---|
| 5편: DB 최적화 | 6편: 모니터링 | 7편: 로깅 |
서론
시스템이 아무리 잘 설계되어도 운영 중 문제가 발생한다. 중요한 건 얼마나 빨리 문제를 파악하고 해결하느냐 다. 이번 편에서는 옵저버빌리티의 핵심 개념과 Metrics 중심의 모니터링 구축 방법을 다룬다.
참고: 로그(Logs)에 대한 상세한 내용은 7편: 로깅 실무 가이드에서 다룬다.
6편에서 다루는 내용:
- 옵저버빌리티의 3가지 축 (Metrics, Logs, Traces)
- Prometheus와 Micrometer를 활용한 메트릭 수집
- 커스텀 비즈니스 메트릭 구현
- 헬스체크와 Kubernetes Probe
- Grafana 대시보드와 알림 설정
목차
- 옵저버빌리티(Observability)란?
- Prometheus & Micrometer
- 메트릭 유형
- 커스텀 비즈니스 메트릭
- 태그(Label)를 활용한 차원 분석
- 헬스체크 (Health Check)
- Grafana 대시보드
- 알림 설정
- FAQ
- 정리
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 | 메모리 사용량, 활성 연결 수 |
| 소요 시간 | Timer | API 응답 시간 |
| 값 분포 | 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편: 로깅 실무 가이드로 이동