2) Spring Boot Actuator

2-1) Actuator 설정

의존성:

// build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'io.micrometer:micrometer-registry-prometheus'  // Prometheus 메트릭
}

application.yml:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus  # 노출할 엔드포인트
  endpoint:
    health:
      show-details: always  # 헬스 체크 상세 정보
  metrics:
    tags:
      application: myapp  # 메트릭에 태그 추가

2-2) 헬스 체크 (Health Check)

기본 헬스 체크:

# http://localhost:8080/actuator/health
curl http://localhost:8080/actuator/health

# 응답:
{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "MySQL",
        "validationQuery": "isValid()"
      }
    },
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 499963174912,
        "free": 123456789012
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}

커스텀 헬스 체크:

@Component
public class ExternalApiHealthIndicator implements HealthIndicator {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public Health health() {
        try {
            // 외부 API 호출
            ResponseEntity<String> response = restTemplate.getForEntity(
                "https://api.example.com/health",
                String.class
            );

            if (response.getStatusCode().is2xxSuccessful()) {
                return Health.up()
                    .withDetail("api", "External API is healthy")
                    .build();
            } else {
                return Health.down()
                    .withDetail("api", "External API returned " + response.getStatusCode())
                    .build();
            }

        } catch (Exception e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

2-3) 메트릭 (Metrics)

기본 메트릭 확인:

# 모든 메트릭 목록
curl http://localhost:8080/actuator/metrics

# 응답:
{
  "names": [
    "jvm.memory.used",
    "jvm.gc.pause",
    "http.server.requests",
    "system.cpu.usage",
    "hikaricp.connections.active"
  ]
}

# 특정 메트릭 상세
curl http://localhost:8080/actuator/metrics/http.server.requests

# 응답:
{
  "name": "http.server.requests",
  "measurements": [
    {
      "statistic": "COUNT",
      "value": 1234
    },
    {
      "statistic": "TOTAL_TIME",
      "value": 12.5
    },
    {
      "statistic": "MAX",
      "value": 0.5
    }
  ],
  "availableTags": [
    {
      "tag": "uri",
      "values": ["/api/users", "/api/orders"]
    },
    {
      "tag": "method",
      "values": ["GET", "POST"]
    },
    {
      "tag": "status",
      "values": ["200", "404", "500"]
    }
  ]
}

커스텀 메트릭:

@Service
public class OrderService {

    private final MeterRegistry meterRegistry;
    private final Counter orderCounter;
    private final Timer orderTimer;

    public OrderService(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;

        // 카운터 (누적 횟수)
        this.orderCounter = Counter.builder("orders.created")
            .description("Number of orders created")
            .tag("type", "online")
            .register(meterRegistry);

        // 타이머 (실행 시간)
        this.orderTimer = Timer.builder("orders.processing.time")
            .description("Time to process an order")
            .register(meterRegistry);
    }

    public Order createOrder(CreateOrderRequest request) {
        return orderTimer.record(() -> {
            // 주문 처리
            Order order = orderRepository.save(request.toEntity());

            // 카운터 증가
            orderCounter.increment();

            // 게이지 (현재 값)
            meterRegistry.gauge("orders.total.amount", order.getAmount());

            return order;
        });
    }
}

메트릭 타입:

// 1. Counter (누적 증가)
Counter counter = Counter.builder("user.login.count")
    .tag("status", "success")
    .register(meterRegistry);
counter.increment();

// 2. Gauge (현재 값)
meterRegistry.gauge("connection.pool.size", connectionPool, ConnectionPool::getSize);

// 3. Timer (실행 시간)
Timer timer = Timer.builder("api.response.time")
    .register(meterRegistry);
timer.record(() -> {
    // 측정할 코드
});

// 4. Distribution Summary (분포)
DistributionSummary summary = DistributionSummary.builder("order.amount")
    .register(meterRegistry);
summary.record(order.getAmount());

📚 다음 편: 준비 중입니다.


👈 이전 편: APM 기본 (Part 1: 개념과 도구)