031 Project 31: Prometheus Exporter
031 Build a Prometheus Exporter
Expose custom application metrics at /metrics using the official Prometheus Go client. Instrument a small HTTP handler with counter, histogram, and gauge—the three metric types you will use constantly.
/work -> update metrics
/metrics -> promhttp scrape text
Problem statement
Build a demo service on :9100:
GET /worksimulates work and updates metricsGET /metricsPrometheus text exposition- Metrics:
demo_requests_total,demo_latency_seconds,demo_inflight
Acceptance criteria
/metricsreturns Prometheus text format- Counter increases on
/work - Histogram observes latency
- Gauge tracks in-flight correctly (inc/dec with defer)
- Module builds with client_golang
Setup
mkdir exporter && cd exporter
go mod init example.com/exporter
go get github.com/prometheus/client_golang/prometheus@latest
go get github.com/prometheus/client_golang/prometheus/promhttp@latest
# go 1.27
go mod tidyFull main.go
package main
import (
"log"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
reqTotal := prometheus.NewCounter(prometheus.CounterOpts{
Name: "demo_requests_total",
Help: "Total completed /work requests",
})
latency := prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "demo_latency_seconds",
Help: "Request latency for /work",
Buckets: prometheus.DefBuckets,
})
inflight := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "demo_inflight",
Help: "In-flight /work requests",
})
prometheus.MustRegister(reqTotal, latency, inflight)
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/work", func(w http.ResponseWriter, r *http.Request) {
inflight.Inc()
defer inflight.Dec()
start := time.Now()
time.Sleep(time.Duration(50+rand.Intn(200)) * time.Millisecond)
reqTotal.Inc()
latency.Observe(time.Since(start).Seconds())
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok\n"))
})
addr := ":9100"
log.Println("listening", addr, " metrics=/metrics work=/work")
log.Fatal(http.ListenAndServe(addr, mux))
}Run and verification
go run .
# terminal B
curl -s localhost:9100/work
curl -s localhost:9100/metrics | grep demo_Expected fragments:
demo_requests_total ...
demo_inflight ...
demo_latency_seconds_bucket ...
Load a bit:
for i in $(seq 1 20); do curl -s localhost:9100/work >/dev/null; done
curl -s localhost:9100/metrics | grep demo_requests_totalStep-by-step build path
- Define metric types and Help strings.
MustRegisterwith default registry.- Instrument handler with defer for gauges.
- Expose
promhttp.Handler(). - Scrape with curl; later add Prometheus scrape config.
Label cardinality warning
// BAD: user_id label → unbounded series
// GOOD: low-cardinality labels: method, code, route templateStretch goals
CounterVecby HTTP method/status.- Custom registry (not global) for tests.
/healthzseparate from metrics.- Exemplars / native histograms (advanced).
Pitfalls
| Pitfall | Fix |
|---|---|
| High-cardinality labels | static enum labels only |
| Forgetting gauge Dec | always defer |
| Blocking default mux pollution | use dedicated ServeMux |
| Measuring after sleep only | observe full handler duration |
Learning goals
- Practical service metrics design
- Counter / histogram / gauge roles
- Observability by default for Go services