
이번 글에서는 Spring Boot 애플리케이션이 생성하는 메트릭을 Actuator로 노출하고, Prometheus가 이를 수집해 Grafana에서 조회하는 구조를 실습한다.

앞선 글의 Spring Boot(Logback) → Loki → Grafana가 애플리케이션이 로그를 즉시 전송하는 push 모델이었다면, Prometheus는 애플리케이션을 주기적으로 호출해 메트릭을 가져오는 pull 모델이다.
애플리케이션은 메트릭을 외부로 전송하지 않는다. 대신 /actuator/prometheus 엔드포인트로 현재 시점의 값을 노출한다. Prometheus는 설정된 주기로 이 엔드포인트를 호출해 데이터를 수집하고, 이를 시계열 형태로 저장한다. Grafana는 Prometheus를 데이터소스로 연결해 PromQL로 계산하고 시각화한다.
Prometheus는 로그 시스템과 구조가 다르다. 로그는 문장을 그대로 저장하고, 이후 키워드로 검색한다. 반면 Prometheus는 문장을 저장하지 않고 숫자만 저장한다. 그리고 그 숫자의 시간에 따른 변화를 기록한다.
예를 들어 로그는 다음과 같이 저장된다.
2026-02-24 10:01:02 ERROR payment failed
반면 Prometheus는 다음과 같은 형태로 저장된다.
http_requests_total{status="500", uri="/pay"} 1
여기에는 메시지가 없고, 메트릭 이름과 숫자, 그리고 이를 구분하기 위한 라벨만 존재한다.
Prometheus에서는 숫자를 구분하기 위해 라벨(label)을 사용한다. 같은 요청 수라도 status=200과 status=500은 서로 다른 시계열로 저장된다. 이를 통해 상태 코드별 집계나 특정 URI 기준 조회가 가능하다.
주의할 점은 라벨을 과도하게 사용하면 안 된다는 것이다. Prometheus는 라벨 조합 하나당 시계열 하나를 생성한다. userId처럼 값이 계속 증가하는 라벨을 사용하면 시계열 수가 급격히 늘어난다. 시계열이 많아질수록 메모리 사용량이 증가하고 성능에 영향을 준다.
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
Actuator는 메트릭 엔드포인트를 노출하는 역할을 한다. micrometer-registry-prometheus는 Spring이 수집한 메트릭을 Prometheus가 이해할 수 있는 포맷으로 변환한다.
spring.application.name=demo
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.prometheus.enabled=true
management.metrics.tags.application=${spring.application.name}
애플리케이션 실행 후 다음 URL에서 메트릭을 확인할 수 있다.
http://localhost:8080/actuator/prometheus
# TYPE http_server_requests_active_seconds summary
http_server_requests_active_seconds_count{application="demo",exception="none",method="GET",outcome="SUCCESS",status="200",uri="UNKNOWN"} 1
http_server_requests_active_seconds_sum{application="demo",exception="none",method="GET",outcome="SUCCESS",status="200",uri="UNKNOWN"} 0.004718125
# HELP http_server_requests_active_seconds_max
# TYPE http_server_requests_active_seconds_max gauge
http_server_requests_active_seconds_max{application="demo",exception="none",method="GET",outcome="SUCCESS",status="200",uri="UNKNOWN"} 0.004731709
# HELP http_server_requests_seconds
# TYPE http_server_requests_seconds summary
http_server_requests_seconds_count{application="demo",error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/actuator/prometheus"} 37
http_server_requests_seconds_sum{application="demo",error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/actuator/prometheus"} 0.365437999
# HELP http_server_requests_seconds_max
# TYPE http_server_requests_seconds_max gauge
http_server_requests_seconds_max{application="demo",error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/actuator/prometheus"} 0.016111958
# HELP jvm_info JVM version info ......
이 엔드포인트는 Prometheus가 주기적으로 호출하는 대상이다.
컨트롤러는 다음과 같이 단순하다.
@RestController
public class DemoController {
@GetMapping("/success")
public String success() {
return "ok";
}
@GetMapping("/error")
public String error() {
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR,
"forced error"
);
}
}
Spring Boot는 기본적으로 HTTP 요청을 자동으로 계측한다. 대표적으로 http_server_requests_seconds_* 계열 메트릭이 생성된다. 이 메트릭은 히스토그램 구조를 가지며, 요청 처리 시간에 대한 정보가 _count, _sum, _bucket 형태로 누적된다.
자주 활용되는 지표는 요청 수와 응답 시간이다.
_count의 증가 속도로 계산한다._sum의 증가 속도를 _count의 증가 속도로 나누어 계산한다._bucket 데이터를 기반으로 근사 계산한다._count와 _sum은 계속 증가하는 누적 값(counter)이다. 예를 들어 /success를 100번 호출하면 _count는 100이 된다. 그러나 이 값만으로는 현재 초당 몇 건이 처리되는지 알 수 없다. Prometheus는 누적 값을 시간 축에 저장한 뒤, rate() 같은 함수를 통해 증가 속도를 계산한다. 이를 통해 누적 데이터를 실제 운영 지표로 변환한다.
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: demo-app
metrics_path: /actuator/prometheus
static_configs:
- targets:
- host.docker.internal:8080
Prometheus는 설정된 주기마다 /actuator/prometheus를 호출한다. 이 동작을 scrape라고 한다. Prometheus UI는 다음 주소에서 확인할 수 있다.
http://localhost:9090
Status → Targets 화면에서 demo-app이 UP이면 메트릭 수집이 정상 동작 중이라는 의미이다. DOWN이면 애플리케이션 주소, 포트, 네트워크 접근 가능 여부, Actuator 노출 설정 등을 점검해야 한다.

Grafana에서 Prometheus를 데이터소스로 등록한다.
http://prometheus:9090연결이 완료되면 PromQL을 사용해 원하는 지표를 시각화할 수 있다. 예를 들어 JVM 메모리 사용량이나 HTTP 요청 수를 그래프로 표현할 수 있다.

상태 코드별 요청 수를 확인하는 기본 쿼리는 다음과 같다.
sum by (status) (
rate(http_server_requests_seconds_count{uri!="/actuator/prometheus"}[1m])
)
_count는 누적 요청 수이므로 rate()를 사용해 초당 증가량으로 변환한다. sum by (status)는 status 기준으로 집계하여 2xx와 5xx 요청을 구분해 보여준다.
평균 응답 시간은 다음과 같이 계산한다.
sum(rate(http_server_requests_seconds_sum{uri!="/actuator/prometheus"}[1m]))
/
sum(rate(http_server_requests_seconds_count{uri!="/actuator/prometheus"}[1m]))
_sum은 처리 시간 누적, _count는 요청 수 누적이다. 두 값을 rate()로 변환한 뒤 나누면 평균 응답 시간이 된다. 평균은 저장된 값이 아니라, 누적 데이터를 조합해 계산한 결과이다.