package main
import (
"log"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"example.com/app/telemetry"
)
func getUser(w http.ResponseWriter, r *http.Request) {
time.Sleep(15 * time.Millisecond)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id":1,"name":"ada"}`))
}
func listOrders(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`[]`))
}
func main() {
mux := http.NewServeMux()
// Route labels are stable templates, never raw paths with IDs.
mux.HandleFunc("/users/", telemetry.Instrument("/users/:id", getUser))
mux.HandleFunc("/orders", telemetry.Instrument("/orders", listOrders))
mux.Handle("/metrics", promhttp.Handler())
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Println("listening on :8080")
log.Fatal(srv.ListenAndServe())
}
package telemetry
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests handled, partitioned by route and status.",
},
[]string{"method", "path", "status"},
)
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Latency of HTTP requests in seconds.",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path"},
)
httpRequestsInFlight = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "http_requests_in_flight",
Help: "Number of HTTP requests currently being served.",
},
)
)
package telemetry
import (
"net/http"
"strconv"
"time"
)
type statusRecorder struct {
http.ResponseWriter
status int
}
func (r *statusRecorder) WriteHeader(code int) {
r.status = code
r.ResponseWriter.WriteHeader(code)
}
// Instrument wraps h and records metrics under a stable, low-cardinality route.
func Instrument(route string, h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
httpRequestsInFlight.Inc()
defer httpRequestsInFlight.Dec()
start := time.Now()
rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
h(rec, r)
elapsed := time.Since(start).Seconds()
status := strconv.Itoa(rec.status)
httpRequestsTotal.WithLabelValues(r.Method, route, status).Inc()
httpRequestDuration.WithLabelValues(r.Method, route).Observe(elapsed)
}
}
This snippet shows how request-level metrics are collected in a Go HTTP service and exposed on a /metrics endpoint that Prometheus can scrape. The core pattern is to register a small set of metric vectors once, update them from middleware on every request, and let the client library serialize them into the Prometheus text exposition format.
In metrics.go, three collectors are declared as package-level vars via promauto, which registers them with the default registry at init time. httpRequestsTotal is a CounterVec labelled by method, path, and status — counters only ever increase, which matches the semantics of a request count. httpRequestDuration is a HistogramVec: rather than storing every latency, a histogram buckets observations into predefined ranges, letting Prometheus compute quantiles like p95 with histogram_quantile. The buckets here use prometheus.DefBuckets, sensible defaults centered on sub-second web latencies. httpRequestsInFlight is a plain Gauge that goes up and down as requests enter and leave.
The cardinality trap is important: labels multiply series, so path must be a low-cardinality route template (/users/:id), never a raw URL with IDs baked in. That is why the middleware takes an explicit route string instead of reading r.URL.Path.
In middleware.go, Instrument wraps a handler. It increments the in-flight gauge with defer to guarantee decrement even on panic, starts a timer, and uses a statusRecorder to capture the status code — necessary because http.ResponseWriter does not expose the code after WriteHeader. When the inner handler returns, WithLabelValues selects the right series and Observe/Inc record the data. time.Since(start).Seconds() feeds the histogram in seconds, the Prometheus convention.
In main.go, routes are registered wrapped in Instrument with stable route labels, and promhttp.Handler() serves the exposition endpoint. That handler does the actual encoding, so no manual formatting is needed. This approach is cheap at runtime, safe for concurrent use since the client library's collectors are goroutine-safe, and scales to production as long as label cardinality stays bounded.
Related snips
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
use tracing::{info, instrument};
#[instrument]
fn process_request(user_id: u64) {
info!(user_id, "Processing request");
// Work happens here
tracing for structured logging and distributed tracing
import pandas as pd
from sklearn.ensemble import IsolationForest
df = pd.read_csv('service_metrics.csv')
features = df[['latency_p95', 'error_rate', 'throughput', 'cpu_utilization']]
Anomaly detection with isolation forest and robust thresholds
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
# Grafana provisioning: datasources
# /etc/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
Grafana dashboards as code with JSON provisioning
Share this code
Here's the card — post it anywhere.