옛날에 작성했던 모니터링은
application 을 따로 Docker 로 띄우거나 Local 환경에서 실행하는 형식을 사용했기 때문에 yml 설정값들을 다시 작성해두기
파일 변경 사항
root/
├── docker-compose.yml # ← Prometheus, Grafana 서비스 추가
├── monitoring/
│ ├── prometheus.yml # ← Prometheus 수집 대상 설정
│ └── grafana/
│ ├── provisioning/
│ │ ├── datasources/
│ │ │ └── datasource.yml # ← Grafana 데이터소스 자동 등록
│ │ └── dashboards/
│ │ └── dashboard.yml # ← 대시보드 로드 설정
│ └── dashboards/
│ ├── for-pets-application.json # ← 실제 대시보드 JSON (export한 것)
│ └── for-pets-business.json
dashboards 의 .json 파일에 dash board 에 만들어둔 panel , 설정 정보들이 저장되기 때문에 꼭 꼭 만들어두기 ...
이번엔 대시보드를 business 랑 application 를 분리했기 때문에 dashboards/ 폴더에 2개가 들어간다.
어차피 dashboard.yml 이 dashboards/ 폴더 전체를 보고 만들기 때문에 상관 없음
docker-compose.yml
변경사항: network 랑 연결해줄 필요가 없어짐 : kafka 에서 이벤트를 발행해서 metrics 를 계산하고 있지 않기 때문
extra_hosts 도 application 파일이 for pets project 내부에 다 있기 때문에 삭제
# --------------------------
# Prometheus
# --------------------------
prometheus:
image: prom/prometheus
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
# --------------------------
# Grafana
# --------------------------
grafana:
image: grafana/grafana
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning
- ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards
depends_on:
- prometheus
volumes:
mysql-data:
redis-data:
grafana_data:
monitoring/prometheus.yml
변경 사항: spring boot 를 docker 밖에서 찾아줄 필요 없기 때문에 targets 를 'host.docker.internal:8080' 에서 변경

# monitoring/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'coffee-service'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['for-pets-app:8080']
# Spring Boot 앱을 도커 내부에서 찾음
최종 프로젝트 구현 부분
이번엔 사실 초기세팅만 하기로 한거라서
저번에 구현했던 부분을 그대로 구현했다.
| title | description | PromQL |
| endpoint 별 RPS | 느린 API 식별 및 트래픽 패턴 파악 | rate(http_server_requests_seconds_count[1m]) |
| endpoint 별 평균 응답 시간 | 병목 감지 | rate(http_server_requests_seconds_sum[1m]) / rate(http_server_requests_seconds_count[1m]) |
| JVM 힙 메모리 | 메모리 누수 감지 | jvm_memory_used_bytes{area="heap"} |
| HikariCP 커넥션 | 커넥션 고갈 및 Deadlock 감지 | hikaricp_connections_active hikaricp_connections_idle hikaricp_connections_pending |
저번에는 method 실행에 걸리는 시간을 측정할 때 MeterRegistry 를 사용했었다
@Service
@RequiredArgsConstructor
public class PaymentService {
private final MeterRegistry meterRegistry;
public void confirmPayment(...) {
Timer.Sample sample = Timer.start(meterRegistry);
try {
// 실제 로직
} finally {
sample.stop(Timer.builder("payment.confirm.duration")
.tag("method", "confirm")
.register(meterRegistry));
}
}
}
요런느낌 -> lock 걸리는 구간 정확하게 시작/끝 지점 지정 가능 근데 복잡함
@Timed(value = "payment.confirm.duration", description = "결제 확인 소요시간")
public void confirmPayment() {
}
Timed 어노테이션 달기 -> 편함 근데 Spring Bean method 에만 적용
상관없긴 하지만 같은 클래스 내부 호출이나 트랜잭션 프록시 안쪽은 못 잡는 경우가 생긴다고 해서 패스
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class ExecutionTimeAspect {
private final MeterRegistry meterRegistry;
/*
* @TrackExecutionTime이 붙은 메서드의 실행 시간 측정
* 성공/실패 여부도 tag로 함께 기록 -> 실패 케이스만 따로 분석 가능
*/
@Around("@annotation(trackExecutionTime)")
public Object measure(ProceedingJoinPoint joinPoint, TrackExecutionTime trackExecutionTime) throws Throwable {
Timer.Sample sample = Timer.start(meterRegistry);
String metricName = trackExecutionTime.value();
String methodName = joinPoint.getSignature().getName();
String status = "success";
try {
return joinPoint.proceed();
} catch (Throwable e) {
status = "failure";
throw e;
} finally {
sample.stop(Timer.builder(metricName + ".duration")
.description(metricName + " 메서드 실행 시간")
.tag("method", methodName)
.tag("status", status)
.register(meterRegistry));
}
}
}
TrackExecutionTime 어노테이션을 직접 만들고
@TrackExecutionTime("payment.confirm")
@Transactional
public ConfirmPaymentResponse confirm(Long memberId, ConfirmPaymentRequest request) {
}
위에 붙여서 사용하면 된다

그라파나 접속
그라파나... 들어가보기 (팀원 공유용)

이 화면에서 3000:3000 되어있는... 저거 클릭

이런 화면이 뜹니다!
email, username: admin
password: admin 으로 들어가면

password 바꾸라는데 그냥 skip

Home 의 Data sources 에서 Prometheus 다운받아서 9090 포트 연결하고 test 해줘요
저는 이미 연결했었어서 default 로 설정되어있습니다
(사실 지금 설정을 해둬서 docker compose 로 실행시키면 제가 만든 dashboard 세팅이 뜨기때문에 거기다 대고 postman 요청같은거 보내보면 panel 들이 자동으로 업데이트 되어요 하하)
Dashboard 를 보면

이미 For pets 가 만들어져있습니다
provisioning/dashboards/dashboard.yml 파일에 folder << 라고 적혀있는곳의 이름으로 뜸

눌러서 create dashboard 를 눌러서

panel 을 추가하면 그 때 부터 추가가 되고, 이후 dashboard 이름을 수정해주고 .... 저장하는게 나와요
add panel 파트에서는

프로메테우스랑 연결하고 PromQL query 랑 필요하다면 options 를 수정해주면 됨

변수는 {{ }} 안에 넣어서 작성해주면 됨

여러개 query를 넣어줄 수도 있다
확인하고 싶은 쿼리 이름 + PromQL 과 이걸 모니터링 해야하는 이유, Options, 추천하는 visualization 을 알려줘
라고 gpt 에게 요청하면 친절하게 알려주니 사용해보기 ㅎㅎ.

친절하시네요...
트러블슈팅
문제
다 만들었는데... 데이터가 안 뜬다

1. target 확인
|
http://localhost:9090/targets
|

down 으로 뜬다
|
http://localhost:8080/actuator/prometheus
|
왜지? 싶어서 actuator 확인
{
"success": false,
"data": null,
"error": {
"timestamp": "2026-05-27T14:32:27.495331196",
"status": 404,
"error": "NOT_FOUND",
"code": "NOT_FOUND",
"message": "요청한 리소스를 찾을 수 없습니다.",
"path": "/actuator/prometheus"
}
}
아예 프로메테우스를 못찾고 있다
분명 build.gradle 과 application.yml 설정에서 잘 되어있다
management:
endpoints:
web:
exposure:
include: health, info, metrics, prometheus
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
...
혹시나 해서
actuator/health 를 체크해봤는데 health 는 되는걸로 보아
이후에 추가한 prometheus 가 docker 에 반영이 안 됨
해결방법
docker rmi for-pets-project-app # 기존 이미지 삭제
docker-compose build --no-cache app # 캐시 없이 새로 빌드
도커를 volume 까지 삭제하고
이미지와 캐시 없이 새로 build 를 진행했다.

정상출력됨!
저번에 volume 과 json 파일을 저장해두지 않아서 다 날라가는... 대참사가 있었는데
| http://localhost:3000/api/dashboards/uid/{uid} |
이 명령어를 사용해서 뜨는 화면을 복사한다음 ai 에게 json 파일로 사용할 수 있도록 수정해달라고 하면 된다.

요런식으로 뜨는걸 복사해서 ai 한테 얘기하면 필요한 부분만 남겨서 바꿔준다


뭐 이런식으로 친절히 알려주는 모습....
근데 이제 uid 를 어디서 확인할 수 잇는지 몰라서 좀 헤맸는데!!
| curl http://admin:admin@localhost:3000/api/search |

uid 를 검색하면 뭔가 잔뜩 뜬다
이 때 7글자 짜리 고르면 된다... ㅎ 야매 방법
나는 이번에 json 파일이 두개니까

요기있음
이 uid 를 아까 위에서 말한 {uid} 자리에 넣어주면 잘 뜬다! ㅎㅎ
끝