Kamu lagi develop API, terus tiba-tiba response time melonjak di salah satu endpoint. Log? Nggak ada yang mencurigakan. Error? Nol. Tapi sesuatu jelas salah — request mulai numpuk, memory naik pelan-pelan, dan kamu baru sadar setelah user complain.
Itu masalah klasik ketika kamu develop tanpa monitoring. Production punya Datadog atau New Relic yang mahal, tapi di local atau staging? Biasanya kosong. Padahal bug performa paling gampang ketangkep justru sebelum deploy.
Solusinya: setup monitoring aplikasi development dengan Prometheus di local, gratis, self-hosted, dan cukup ringan buat jalan di laptop.
Kenapa Prometheus dan Bukan yang Lain?
Prometheus itu pull-based — dia yang aktif scrape metrics dari aplikasi kamu, bukan aplikasi yang push ke server monitoring. Ini bikin setup lebih simpel: aplikasi kamu cukup expose endpoint /metrics, sisanya urusan Prometheus.
Bandingkan dengan setup push (StatsD, InfluxDB + Telegraf): kamu harus config agent di setiap service, atur interval push, handle backpressure. Ribet untuk dev environment yang sering restart-restart.
Selain itu, Prometheus punya query language sendiri (PromQL) yang powerful buat slice-and-dice metrics, dan integrasinya dengan Grafana sudah sangat mature.
Satu catatan: Prometheus bukan solusi untuk log aggregation. Kalau kamu butuh trace log per-request, tetap pakai Loki atau ELK. Prometheus fokus ke time-series metrics — counter, gauge, histogram, summary.
Setup Stack: Prometheus + Grafana dengan Docker Compose
Kita pakai Docker Compose supaya gampang naik-turunin stack-nya. Buat direktori project:
mkdir prom-dev-stack && cd prom-dev-stack
mkdir -p prometheus grafana/provisioning/datasources
Buat file prometheus/prometheus.yml:
global:
scrape_interval: 5s
evaluation_interval: 5s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'my-api'
static_configs:
- targets: ['host.docker.internal:8000']
metrics_path: '/metrics'
Perhatikan host.docker.internal — ini buat akses ke aplikasi yang jalan di host machine dari dalam container Docker. Di Linux, ini kadang nggak work out-of-the-box (gotcha-nya nanti).
Buat file grafana/provisioning/datasources/prometheus.yml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
Sekarang buat docker-compose.yml di root direktori:
version: '3.8'
services:
prometheus:
image: prom/prometheus:v2.51.0
container_name: prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- '9090:9090'
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=7d'
extra_hosts:
- 'host.docker.internal:host-gateway'
grafana:
image: grafana/grafana:10.4.0
container_name: grafana
ports:
- '3000:3000'
environment:
- GF_SECURITY_ADMIN_PASSWORD=devpassword
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
depends_on:
- prometheus
volumes:
grafana-data:
Line extra_hosts: host.docker.internal:host-gateway itu yang fix masalah Linux tadi. Tanpa ini, Prometheus di dalam container nggak bisa reach aplikasi di host.
Jalankan:
docker compose up -d
Prometheus bisa diakses di http://localhost:9090, Grafana di http://localhost:3000 (login: admin / devpassword).
Instrumentasi Aplikasi: Contoh dengan FastAPI
Sekarang kita instrumentasi aplikasi supaya expose /metrics. Contoh ini pakai Python + FastAPI, tapi konsepnya sama untuk bahasa lain.
Install dependency:
pip install fastapi uvicorn prometheus-client
Buat main.py:
from fastapi import FastAPI, Request
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
from fastapi.responses import Response
import time
import random
app = FastAPI()
# Definisi metrics
REQUEST_COUNT = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status_code']
)
REQUEST_LATENCY = Histogram(
'http_request_duration_seconds',
'HTTP request latency',
['method', 'endpoint'],
buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5]
)
@app.middleware('http')
async def metrics_middleware(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
duration = time.time() - start_time
# Skip endpoint /metrics itu sendiri
if request.url.path != '/metrics':
REQUEST_COUNT.labels(
method=request.method,
endpoint=request.url.path,
status_code=response.status_code
).inc()
REQUEST_LATENCY.labels(
method=request.method,
endpoint=request.url.path
).observe(duration)
return response
@app.get('/metrics')
def metrics():
return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)
@app.get('/api/users')
def get_users():
# Simulasi latency random
time.sleep(random.uniform(0.01, 0.3))
return {'users': ['alice', 'bob', 'charlie']}
@app.get('/api/slow')
def slow_endpoint():
time.sleep(random.uniform(0.5, 2.0))
return {'status': 'done'}
Jalankan aplikasi:
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
Cek apakah metrics sudah muncul:
curl http://localhost:8000/metrics
Kamu harusnya lihat output teks dengan format Prometheus — banyak baris # HELP, # TYPE, dan nilai metrics.
Query dengan PromQL: Yang Paling Sering Dipakai
Buka Prometheus UI di http://localhost:9090/graph. Coba beberapa query ini:
Request rate per endpoint (per 1 menit):
rate(http_requests_total[1m])
P95 latency per endpoint:
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
Total error rate (status 5xx):
rate(http_requests_total{status_code=~"5.."}[5m])
Perbandingan latency antar endpoint:
histogram_quantile(0.99, sum by (endpoint, le) (rate(http_request_duration_seconds_bucket[5m])))
Query terakhir itu yang paling berguna waktu debug — langsung keliatan endpoint mana yang P99-nya jelek.
Gotcha yang Pernah Gue Kena
1. host.docker.internal nggak resolve di Linux
Ini yang paling sering bikin frustrasi. Di Mac dan Windows, Docker Desktop otomatis setup DNS untuk host.docker.internal. Di Linux dengan Docker Engine biasa, kamu harus tambah extra_hosts di docker-compose.yml seperti yang sudah ada di config di atas. Kalau masih nggak jalan, coba cek IP host kamu:
ip route show default | awk '{print $3}'
Terus hardcode IP itu di prometheus.yml:
static_configs:
- targets: ['172.17.0.1:8000']
2. Scrape target berstatus DOWN
Cek di http://localhost:9090/targets. Kalau status merah, biasanya masalahnya:
- Aplikasi belum jalan atau port salah
- Endpoint
/metricsbutuh auth (tambahbasic_authdi scrape config) - Firewall block koneksi
3. Histogram bucket terlalu kasar
Default bucket Prometheus itu .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10. Kalau API kamu biasanya respond dalam 10–50ms, bucket default terlalu lebar — semua request masuk bucket yang sama, P95 jadi nggak akurat. Definisikan bucket sendiri sesuai karakteristik aplikasi kamu, seperti di contoh kode di atas.
4. Cardinality explosion
Jangan pernah taruh nilai yang high-cardinality sebagai label — misalnya user_id, request_id, atau URL dengan path parameter (/api/users/12345). Ini bikin Prometheus makan memory gila-gilaan karena setiap kombinasi label unik disimpan sebagai time series tersendiri.
Contoh yang salah:
# JANGAN lakukan ini
REQUEST_COUNT.labels(
endpoint=request.url.path # /api/users/1, /api/users/2, dst.
).inc()
Solusinya: normalize path sebelum dijadikan label, atau pakai routing pattern (/api/users/{id}).
Setup Dashboard Grafana
Login ke Grafana (http://localhost:3000), datasource Prometheus sudah auto-provisioned. Buat dashboard baru, tambah panel dengan query:
- Request Rate:
sum(rate(http_requests_total[1m])) by (endpoint) - Error Rate:
sum(rate(http_requests_total{status_code=~"5.."}[1m])) by (endpoint) - P95 Latency:
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (endpoint, le))
Kalau malas bikin dari scratch, import dashboard community. Di Grafana, pergi ke Dashboards → Import, masukkan ID 1860 (Node Exporter Full) atau cari di grafana.com/grafana/dashboards dengan keyword "FastAPI" atau "Flask".
Untuk FastAPI spesifik, ID 16110 lumayan bagus sebagai starting point.
Tambah Node Exporter untuk System Metrics
Kalau kamu juga mau monitor CPU, memory, dan disk dari mesin dev kamu, tambah Node Exporter ke docker-compose.yml:
node-exporter:
image: prom/node-exporter:v1.7.0
container_name: node-exporter
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
ports:
- '9100:9100'
Tambah job scrape di prometheus.yml:
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
Restart stack:
docker compose down && docker compose up -d
Yang Gue Lakuin Setelah Setup Ini
Setelah stack ini jalan, gue biasanya langsung:
- Jalankan load test ringan pakai
heyatauk6ke endpoint yang lagi dikembangkan, sambil lihat dashboard Grafana real-time. Langsung keliatan kalau ada endpoint yang latency-nya aneh atau memory naik terus.
# Install hey
go install github.com/rakyll/hey@latest
# Load test 200 request, 10 concurrent
hey -n 200 -c 10 http://localhost:8000/api/users
-
Export config ini ke repo sebagai
monitoring/directory, jadi semua developer di tim bisa langsung pakai dengandocker compose up. -
Replikasi setup yang sama di staging — bedanya cuma scrape target-nya yang diganti ke IP server staging. Sama persis, zero surprise waktu deploy.
Monitoring aplikasi development dengan Prometheus memang butuh effort awal setup, tapi setelah jalan, kamu punya visibility yang sebelumnya cuma ada di production. Bug performa yang biasanya baru ketahuan setelah deploy sekarang bisa ketangkep di local — jauh lebih murah biaya fixnya.