Operations 20 min read

How to Build High‑Quality Prometheus Exporters: From Basics to Custom Go Implementations

This article explains the concept of Prometheus exporters, details the four metric types, provides step‑by‑step Go code for creating custom exporters and collectors, and outlines best practices for designing robust, production‑ready monitoring exporters.

Ops Development Stories
Ops Development Stories
Ops Development Stories
How to Build High‑Quality Prometheus Exporters: From Basics to Custom Go Implementations

Exporter Introduction

Exporter is a component that collects monitoring data from target systems and exposes it in Prometheus format; Prometheus scrapes the /metrics endpoint periodically. It acts as a translator that normalizes various system metrics into a unified language.

The main functions are: encapsulate modules to obtain internal statistics, normalize data to Prometheus format, and expose the collected metrics via an HTTP endpoint.

Encapsulate functionality to fetch internal statistics.

Map returned data to Prometheus‑compatible format.

Collector stores normalized data and the HTTP /metrics endpoint serves it to Prometheus.

Prometheus Client Overview

The Go client library implements an HTTP server that responds to Prometheus scrape requests. Metrics are exposed as text lines with # HELP and # TYPE annotations followed by metric samples.

<code># HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0.5"} 0.000107458
go_gc_duration_seconds{quantile="0.75"} 0.000200112
go_gc_duration_seconds{quantile="1"} 0.000299278
go_gc_duration_seconds_sum 0.002341738
go_gc_duration_seconds_count 18
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 107
</code>

Four metric types are supported: Counter, Gauge, Histogram, and Summary, each with specific use‑cases.

Write Your Own Exporter

Most exporters are written in Go. The following example creates a gauge and a CounterVec, registers them, and starts an HTTP server on port 8888.

<code>package main

import (
    "log"
    "net/http"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
        Namespace: "our_idc",
        Subsystem: "k8s",
        Name:      "cpu_temperature_celsius",
        Help:      "Current temperature of the CPU.",
    })
    hdFailures = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Namespace: "our_idc",
            Subsystem: "k8s",
            Name:      "hd_errors_total",
            Help:      "Number of hard‑disk errors.",
        },
        []string{"device"},
    )
)

func init() {
    prometheus.MustRegister(cpuTemp)
    prometheus.MustRegister(hdFailures)
}

func main() {
    cpuTemp.Set(65.3)
    hdFailures.With(prometheus.Labels{"device": "/dev/sda"}).Inc()
    http.Handle("/metrics", promhttp.Handler())
    log.Fatal(http.ListenAndServe(":8888", nil))
}
</code>

After running, the metrics are available at http://localhost:8888/metrics.

Custom Collector

A custom collector implements the prometheus.Collector interface with Describe and Collect methods. The Collect method gathers data, converts it to the appropriate ValueType, and sends it to the channel.

<code>type ClusterManager struct {
    sync.Mutex
    Zone              string
    metricMapCounters map[string]string
    metricMapGauges   map[string]string
}

func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) { /* ... */ }
func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) { /* ... */ }
</code>

Best Practices for High‑Quality Exporters

Provide a landing page with documentation and health checks.

Avoid port conflicts; use non‑default ports when necessary.

Design metric names and labels following Prometheus naming conventions.

Expose exporter’s own health metrics.

Make features configurable and use YAML for configuration.

Use proper units and avoid duplicate metrics.

Prefer collectors over static const metrics when possible.

Report scrape errors to aid debugging.

Redis Exporter Source Analysis

The Redis exporter runs on port 9192 by default and exposes metrics at /metrics. Command‑line flags can change the listen address and telemetry path.

<code>redis_exporter -web.listen-address=":8888" -web.telemetry-path="/node_metrics"
</code>

It uses the Redis INFO command to gather data, parses the output, and registers metrics such as db_keys, db_keys_expiring, and instance_info.

<code>func (e *Exporter) extractInfoMetrics(ch chan<- prometheus.Metric, info string, dbCount int) { /* ... */ }
</code>

Parsing functions convert string values to floats, map them to Counter or Gauge types, and register them via registerConstMetric.

<code>func (e *Exporter) registerConstMetric(ch chan<- prometheus.Metric, metric string, val float64, valType prometheus.ValueType, labelValues ...string) { /* ... */ }
</code>
MonitoringGoMetricsPrometheusExporter
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

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.