package main import ( "encoding/json" "log/slog" "net/http" "os" "strconv" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" ) var ( reqTotal = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests.", }, []string{"method", "path", "status"}) reqDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request latency in seconds.", Buckets: prometheus.DefBuckets, }, []string{"method", "path"}) ) func main() { env := getenv("APP_ENV", "dev") version := getenv("APP_VERSION", "unknown") port := getenv("PORT", "8080") logLevel := slog.LevelInfo if getenv("LOG_LEVEL", "info") == "debug" { logLevel = slog.LevelDebug } slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel}))) mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) mux.HandleFunc("/", instrument(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "env": env, "version": version, }) })) slog.Info("listening", "port", port, "env", env, "version", version) if err := http.ListenAndServe(":"+port, mux); err != nil { slog.Error("server failed", "err", err) os.Exit(1) } } func instrument(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { rw := &statusWriter{ResponseWriter: w, status: http.StatusOK} start := time.Now() next(rw, r) dur := time.Since(start).Seconds() status := strconv.Itoa(rw.status) reqTotal.WithLabelValues(r.Method, r.URL.Path, status).Inc() reqDuration.WithLabelValues(r.Method, r.URL.Path).Observe(dur) slog.Debug("request", "method", r.Method, "path", r.URL.Path, "status", rw.status, "duration", dur) } } type statusWriter struct { http.ResponseWriter status int } func (sw *statusWriter) WriteHeader(code int) { sw.status = code sw.ResponseWriter.WriteHeader(code) } func getenv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }