Как вы бы организовали мониторинг и сбор метрик в Go-приложении? Расскажите об опыте с Prometheus.

Ответ

Для организации мониторинга в Go-приложениях я придерживаюсь подхода, основанного на обсервабилити (observability), который включает три столпа: метрики, логирование и трассировку. Для сбора метрик стандартом де-факто в cloud-native среде является Prometheus.

Да, я работал с официальной клиентской библиотекой prometheus/client_golang. Она позволяет инструментировать приложение, то есть встраивать в код сбор метрик, и предоставлять их по HTTP-эндпоинту /metrics для сбора сервером Prometheus.

Основные типы метрик и их применение:

  • Counter: Монотонно растущий счетчик. Идеален для подсчета общего числа запросов, количества ошибок, обработанных задач. Пример: http_requests_total.
  • Gauge: Значение, которое может как увеличиваться, так и уменьшаться. Используется для измерения текущих значений. Пример: current_active_goroutines, cpu_temperature.
  • Histogram: Измеряет распределение наблюдений по заданным корзинам (бакетам). Отлично подходит для измерения времени ответа (latency) или размера запроса. Позволяет вычислять квантили (например, 95-й или 99-й перцентиль) на стороне сервера Prometheus.
  • Summary: Похож на Histogram, но вычисляет квантили на стороне клиента. Используется реже из-за большей нагрузки на приложение.

Ключевой особенностью Prometheus является использование лейблов (labels) — пар ключ-значение для разделения метрик. Это позволяет создавать многомерные данные. Например, можно отслеживать HTTP-запросы не просто как общее число, а с разбивкой по методу, пути и коду ответа: http_requests_total{method="POST", path="/api/users", code="201"}.

Пример базовой интеграции:

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

// Используем promauto для автоматической регистрации метрик
var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "myapp_http_requests_total",
            Help: "Total number of HTTP requests.",
        },
        []string{"method", "path"}, // Лейблы для разделения
    )

    httpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "myapp_http_request_duration_seconds",
            Help:    "Duration of HTTP requests.",
            Buckets: prometheus.DefBuckets, // Стандартные бакеты
        },
        []string{"path"},
    )
)

// Middleware для инструментирования HTTP-хендлеров
func prometheusMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        timer := prometheus.NewTimer(httpRequestDuration.WithLabelValues(r.URL.Path))
        httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path).Inc()
        next.ServeHTTP(w, r)
        timer.ObserveDuration()
    })
}

func main() {
    // Оборачиваем наши хендлеры в middleware
    mainMux := http.NewServeMux()
    mainMux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })

    // Отдельный эндпоинт для метрик
    http.Handle("/metrics", promhttp.Handler())
    http.Handle("/", prometheusMiddleware(mainMux))

    http.ListenAndServe(":8080", nil)
}

Этот подход с использованием middleware позволяет легко добавлять мониторинг ко всем HTTP-эндпоинтам, не загромождая бизнес-логику.