Operations 17 min read

Using the Prometheus Go Client Library to Instrument Go Applications

This tutorial demonstrates how to create a simple Go application, expose the default Prometheus metrics endpoint, add custom gauge metrics, use a custom registry, and work with counters, histograms, summaries, and label vectors to fully instrument Go services for monitoring.

DevOps Cloud Academy
DevOps Cloud Academy
DevOps Cloud Academy
Using the Prometheus Go Client Library to Instrument Go Applications

First, create a minimal Go application that serves the default Prometheus metrics registry on the /metrics endpoint at port 8080. Initialize the module with go mod init github.com/cnych/instrument-demo and add a main.go file containing a basic HTTP server that registers the default handler:

package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    // Serve the default Prometheus metrics registry over HTTP on /metrics.
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":8080", nil)
}

Run the program with go run main.go and visit http://localhost:8080/metrics to see the automatically exported runtime metrics such as go_gc_duration_seconds , go_goroutines , and promhttp_metric_handler_requests_total .

To add a custom metric, create a new file custom-metric/main.go and define a gauge metric that reports the current temperature:

package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    // Create a gauge metric without any label.
    temp := prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "home_temperature_celsius",
        Help: "The current temperature in degrees Celsius.",
    })
    prometheus.MustRegister(temp)
    temp.Set(39)
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":8080", nil)
}

After running the program, the home_temperature_celsius metric appears alongside the default metrics.

If you prefer not to use the global default registry, create a custom registry with prometheus.NewRegistry() , optionally register process and Go runtime collectors, and expose it via promhttp.HandlerFor :

package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    // Create a custom registry.
    registry := prometheus.NewRegistry()
    registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
    registry.MustRegister(prometheus.NewGoCollector())

    // Create a gauge metric.
    temp := prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "home_temperature_celsius",
        Help: "The current temperature in degrees Celsius.",
    })
    registry.MustRegister(temp)
    temp.Set(39)

    // Expose the custom registry.
    http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{Registry: registry}))
    http.ListenAndServe(":8080", nil)
}

Both the default‑registry and custom‑registry examples produce identical metric output when queried.

Prometheus provides several metric types. A Gauge can be set, incremented, decremented, added to, or subtracted from using methods like Set() , Inc() , Dec() , Add() , and Sub() . Example:

queueLength := prometheus.NewGauge(prometheus.GaugeOpts{
    Name: "queue_length",
    Help: "The number of items in the queue.",
})
queueLength.Set(0)
queueLength.Inc()
queueLength.Dec()
queueLength.Add(23)
queueLength.Sub(42)

A Counter only increases; use Inc() or Add() to record events such as HTTP request totals:

totalRequests := prometheus.NewCounter(prometheus.CounterOpts{
    Name: "http_requests_total",
    Help: "The total number of handled HTTP requests.",
})
totalRequests.Inc()
totalRequests.Add(23)

A Histogram groups observations into configurable buckets. Define bucket boundaries with Buckets or helper functions like prometheus.LinearBuckets() . Record observations with Observe() or use prometheus.NewTimer() to time code sections automatically.

requestDurations := prometheus.NewHistogram(prometheus.HistogramOpts{
    Name: "http_request_duration_seconds",
    Help: "A histogram of the HTTP request durations in seconds.",
    Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
})
requestDurations.Observe(0.42)
// Timing helper
timer := prometheus.NewTimer(requestDurations)
// ...handle request...
timer.ObserveDuration()

A Summary tracks configurable quantiles without buckets. Define desired quantiles in Objectives and record values with Observe() :

requestDurations := prometheus.NewSummary(prometheus.SummaryOpts{
    Name: "http_request_duration_seconds",
    Help: "A summary of the HTTP request durations in seconds.",
    Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
})
requestDurations.Observe(0.42)

To add dimensions (labels) to metrics, use the New*Vec constructors (e.g., NewGaugeVec , NewCounterVec , NewHistogramVec , NewSummaryVec ) and specify a slice of label names. Access a specific labeled metric with WithLabelValues() or With() :

temp := prometheus.NewGaugeVec(
    prometheus.GaugeOpts{
        Name: "home_temperature_celsius",
        Help: "The current temperature in degrees Celsius.",
    },
    []string{"house", "room"},
)
prometheus.MustRegister(temp)
temp.WithLabelValues("cnych", "living-room").Set(27)
temp.With(prometheus.Labels{"house": "ydzs", "room": "bedroom"}).Set(27.7)

Running the labeled‑metric example and querying /metrics shows separate time series for each house and room combination.

Note: A labeled time series only appears in the /metrics output after it has been observed at least once; you may need to initialize important label combinations on startup.
monitoringInstrumentationGometricsPrometheusGaugecustom-registry
DevOps Cloud Academy
Written by

DevOps Cloud Academy

Exploring industry DevOps practices and technical expertise.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.