Назад
Golang

Мониторинг в Go: когда ваш сервис прилег

Сегодня поговорим о способах мониторинга состояния в Go-приложениях: метриках и логах.

Чем отличаются метрики и логи?

Логи — это детальные записи о событиях в приложении, хронология происходящего.

Метрики — числовые показатели, которые можно агрегировать и анализировать.

Логирование с zap - быстрее стандартного log. Uber создал zap не просто так. Стандартный log в Go медленный из-за кучи аллокаций памяти.

Проблема стандартного логгера:

log.Printf("User %s failed to login from IP %s", username, ip)
// Каждый вызов создаёт строку в heap -> нагрузка на GC

Решение с zap:

import "go.uber.org/zap"

func main() {
    // Production-конфиг: JSON + быстрая работа
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    
    // Структурированное логирование
    logger.Info("User login failed",
        zap.String("username", username),
        zap.String("ip", ip),
        zap.Int("attempt", 3),
        zap.Duration("response_time", 105*time.Millisecond),
    )
    
    // Sugar-синтаксис для удобства
    sugar := logger.Sugar()
    sugar.Infof("User %s failed to login from %s", username, ip)
}

Что это даёт?

· В 10+ раз быстрее стандартного логгера
· Структурированный вывод (JSON)
· Контекстные поля вместо склеивания строк
· Разные уровни логирования (Debug, Info, Warn, Error)

Но зачем же тогда нужны метрики?

Prometheus стал стандартом для сбора метрик в Kubernetes-мире.

Типичные проблемы, когда нет метрик:
· "Почему сервис медленный?" — непонятно
· "Сколько ошибок в час?" — нужно парсить логи
· "Когда масштабировать?" — гадание на кофейной гуще

Решение с prometheus:

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

var (
    // Counter — только увеличивается (запросы, ошибки)
    requestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path", "status"},
    )
    
    // Histogram — распределение значений (время ответа)
    responseTime = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_response_time_seconds",
            Help:    "HTTP response time distribution",
            Buckets: prometheus.DefBuckets, // предопределённые корзины
        },
        []string{"method", "path"},
    )
    
    // Gauge — может увеличиваться и уменьшаться (текущие соединения)
    activeConnections = prometheus.NewGauge(
        prometheus.GaugeOpts{
            Name: "active_connections",
            Help: "Number of active connections",
        },
    )
)

func init() {
    prometheus.MustRegister(requestsTotal, responseTime, activeConnections)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    
    // Логика обработки запроса
    // ...
    
    // Запись метрик
    requestsTotal.WithLabelValues(r.Method, r.URL.Path, "200").Inc()
    responseTime.WithLabelValues(r.Method, r.URL.Path).Observe(time.Since(start).Seconds())
    activeConnections.Inc()
    defer activeConnections.Dec()
}

Экспорт метрик:

// Отдельный эндпоинт для Prometheus (нужно запустить в отдельной горутине)
http.Handle("/metrics", promhttp.Handler())
go http.ListenAndServe(":2112", nil)

// Теперь Prometheus может скрапировать метрики с localhost:2112/metrics
1