Мониторинг в 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