prom-client (npm) → github.com/prometheus/client_golang
new prom.Counter() → prometheus.NewCounter()
new prom.Histogram() → prometheus.NewHistogram()
new prom.Gauge() → prometheus.NewGauge()
app.get('/metrics', ...) → promhttp.Handler()
prom.register.register(counter) → prometheus.MustRegister(counter)
/metrics для Prometheusmkdir go-metrics && cd go-metrics
go mod init go-metrics
# Устанавливаем зависимости
go get github.com/prometheus/client_golang
go get github.com/google/uuid
go mod tidy
# Запускаем Prometheus (опционально)
docker run -d --name prometheus \
-p 9090:9090 \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus
prometheus.ymlglobal:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'go-metrics-app'
static_configs:
- targets: ['host.docker.internal:8080']
internal/metrics/metrics.gopackage metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
// ╔══════════════════════════════════════════════════════════╗
// ║ МЕТРИКИ (используем promauto — авто-регистрация) ║
// ╚══════════════════════════════════════════════════════════╝
var (
// ==========================================
// HTTP метрики
// ==========================================
// HTTPRequestsTotal — СЧЁТЧИК общего количества HTTP-запросов.
// CounterVec — счётчик с лейблами (method, path, status).
HTTPRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
// HTTPRequestDuration — ГИСТОГРАММА длительности запросов.
// Buckets — границы для распределения (в секундах).
HTTPRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
},
[]string{"method", "path"},
)
// HTTPRequestsInFlight — GAUGE текущего количества запросов в обработке
HTTPRequestsInFlight = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "http_requests_in_flight",
Help: "Current number of HTTP requests being served",
},
)
// HTTPResponseSize — ГИСТОГРАММА размера ответа
HTTPResponseSize = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_response_size_bytes",
Help: "HTTP response size in bytes",
Buckets: prometheus.ExponentialBuckets(100, 2, 12), // 100, 200, 400, ... 409600
},
[]string{"method", "path"},
)
// ==========================================
// Бизнес-метрики
// ==========================================
// OrdersCreatedTotal — количество созданных заказов
OrdersCreatedTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "orders_created_total",
Help: "Total number of orders created",
},
[]string{"status"}, // success, failed
)
// OrdersAmountTotal — общая сумма заказов
OrdersAmountTotal = promauto.NewCounter(
prometheus.CounterOpts{
Name: "orders_amount_total",
Help: "Total amount of orders in USD",
},
)
// UsersRegisteredTotal — количество регистраций
UsersRegisteredTotal = promauto.NewCounter(
prometheus.CounterOpts{
Name: "users_registered_total",
Help: "Total number of user registrations",
},
)
// ActiveUsers — GAUGE активных пользователей
ActiveUsers = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "active_users_current",
Help: "Current number of active users",
},
)
// ==========================================
// Метрики БД
// ==========================================
// DBQueriesTotal — счётчик запросов к БД
DBQueriesTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "db_queries_total",
Help: "Total number of database queries",
},
[]string{"operation", "status"}, // select/insert/update/delete, success/error
)
// DBQueryDuration — гистограмма времени запросов
DBQueryDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "db_query_duration_seconds",
Help: "Database query duration in seconds",
Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1},
},
[]string{"operation"},
)
// DBPoolConnections — GAUGE соединений в пуле
DBPoolConnections = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "db_pool_connections",
Help: "Database pool connections",
},
[]string{"state"}, // idle, active, total
)
// ==========================================
// Метрики Redis
// ==========================================
// CacheHitsTotal — попадания в кэш
CacheHitsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "Total number of cache hits/misses",
},
[]string{"result"}, // hit, miss
)
// ==========================================
// Метрики приложения
// ==========================================
// AppVersion — версия приложения (info-метрика)
AppVersion = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "app_info",
Help: "Application information",
ConstLabels: prometheus.Labels{
"version": "1.0.0",
"language": "go",
},
},
)
)
// ╔══════════════════════════════════════════════════════════╗
// ║ ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ║
// ╚══════════════════════════════════════════════════════════╝
// RecordOrderCreated — записывает метрику создания заказа
func RecordOrderCreated(success bool, amount float64) {
status := "success"
if !success {
status = "failed"
}
OrdersCreatedTotal.WithLabelValues(status).Inc()
if success {
OrdersAmountTotal.Add(amount)
}
}
// RecordDBQuery — записывает метрику запроса к БД
func RecordDBQuery(operation string, duration float64, err error) {
status := "success"
if err != nil {
status = "error"
}
DBQueriesTotal.WithLabelValues(operation, status).Inc()
DBQueryDuration.WithLabelValues(operation).Observe(duration)
}
// RecordCacheResult — записывает попадание/промах кэша
func RecordCacheResult(hit bool) {
result := "miss"
if hit {
result = "hit"
}
CacheHitsTotal.WithLabelValues(result).Inc()
}
internal/middleware/metrics.gopackage middleware
import (
"net/http"
"strconv"
"time"
"go-metrics/internal/metrics"
)
// MetricsMiddleware — собирает HTTP-метрики
func MetricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Увеличиваем счётчик активных запросов
metrics.HTTPRequestsInFlight.Inc()
defer metrics.HTTPRequestsInFlight.Dec()
// Оборачиваем ResponseWriter
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// Выполняем запрос
next.ServeHTTP(wrapped, r)
// Собираем метрики
duration := time.Since(start).Seconds()
status := strconv.Itoa(wrapped.statusCode)
path := r.URL.Path // В реальности — нормализованный путь (не /users/123, а /users/{id})
metrics.HTTPRequestsTotal.WithLabelValues(r.Method, path, status).Inc()
metrics.HTTPRequestDuration.WithLabelValues(r.Method, path).Observe(duration)
metrics.HTTPResponseSize.WithLabelValues(r.Method, path).Observe(float64(wrapped.bytesWritten))
})
}
type responseWriter struct {
http.ResponseWriter
statusCode int
bytesWritten int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
n, err := rw.ResponseWriter.Write(b)
rw.bytesWritten += n
return n, err
}
cmd/server/main.gopackage main
import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go-metrics/internal/metrics"
"go-metrics/internal/middleware"
)
func main() {
fmt.Println("📊 Сервер с Prometheus-метриками на :8080")
// Инициализируем версию приложения
metrics.AppVersion.Set(1)
mux := http.NewServeMux()
// Эндпоинт для метрик Prometheus
mux.Handle("/metrics", promhttp.Handler())
// Health check
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
// Бизнес-эндпоинт: создание заказа
mux.HandleFunc("POST /api/orders", func(w http.ResponseWriter, r *http.Request) {
// Имитация бизнес-логики
success := rand.Float64() > 0.2 // 80% успех
amount := float64(rand.Intn(10000)) / 100 // $0-$99.99
metrics.RecordOrderCreated(success, amount)
if success {
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, `{"status":"created","amount":%.2f}`, amount)
} else {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"status":"error"}`))
}
})
// Бизнес-эндпоинт: регистрация
mux.HandleFunc("POST /api/register", func(w http.ResponseWriter, r *http.Request) {
metrics.UsersRegisteredTotal.Inc()
metrics.ActiveUsers.Inc() // В реальности — через фоновый сбор
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`{"status":"registered"}`))
})
// Эндпоинт с имитацией запроса к БД
mux.HandleFunc("GET /api/users/{id}", func(w http.ResponseWriter, r *http.Request) {
// Имитация запроса к БД
dbStart := time.Now()
time.Sleep(time.Duration(rand.Intn(50)) * time.Millisecond)
dbDuration := time.Since(dbStart).Seconds()
dbErr := error(nil)
if rand.Float64() < 0.1 {
dbErr = errors.New("timeout")
}
metrics.RecordDBQuery("select", dbDuration, dbErr)
metrics.RecordCacheResult(rand.Float64() > 0.3) // 70% cache hit
if dbErr != nil {
http.Error(w, "DB error", http.StatusInternalServerError)
return
}
id := r.PathValue("id")
fmt.Fprintf(w, `{"user":"%s"}`, id)
})
// Применяем middleware
handler := middleware.MetricsMiddleware(mux)
server := &http.Server{
Addr: ":8080",
Handler: handler,
}
// Graceful shutdown
go func() {
fmt.Println("✅ Сервер запущен")
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
os.Exit(1)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("\n🛑 Выключение...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
fmt.Println("✅ Остановлен")
}
# Запуск сервера
go run ./cmd/server/main.go
# Проверка метрик (другой терминал)
curl http://localhost:8080/metrics
# Генерация нагрузки
for i in {1..100}; do
curl -s http://localhost:8080/api/users/$i > /dev/null &
curl -s -X POST http://localhost:8080/api/orders > /dev/null &
done
wait
# Смотрим метрики после нагрузки
curl -s http://localhost:8080/metrics | grep -E "http_requests_total|orders_created_total|db_queries_total"
# Запуск Prometheus (в Docker)
docker run -d --name prometheus \
-p 9090:9090 \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus
# Открыть Prometheus: http://localhost:9090
# Запросы в PromQL:
# rate(http_requests_total[1m])
# histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[1m]))
# sum(rate(orders_created_total[1m])) by (status)
| Тип | Поведение | Пример | PromQL |
|---|---|---|---|
| Counter | Только растёт (сбрасывается при рестарте) | http_requests_total | rate(http_requests_total[1m]) |
| Gauge | Растёт и падает | http_requests_in_flight | avg_over_time(memory_bytes[5m]) |
| Histogram | Распределение по bucket’ам | http_request_duration_seconds | histogram_quantile(0.95, rate(...)) |
| Summary | Квантили на клиенте | request_latency_seconds | request_latency_seconds{quantile="0.95"} |
| Метрика | PromQL |
|---|---|
| RPS (запросов в секунду) | rate(http_requests_total[1m]) |
| Процент ошибок | rate(http_requests_total{status=~"5.."}[1m]) / rate(http_requests_total[1m]) * 100 |
| P95 latency | histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[1m])) |
| Активные соединения | db_pool_connections{state="active"} |
| Cache hit rate | rate(cache_hits_total{result="hit"}[1m]) / rate(cache_hits_total[1m]) * 100 |
💡 Best practices от сеньоров:
/users/{id} в лейблах. Иначе бесконечный рост time series.💡 Для Node.js разработчика:
prom-client в Node.js и client_golang в Go — почти идентичное API.
new prom.Counter() + авто-регистрация.app.get('/metrics', async (req, res) => { res.send(await prom.register.metrics()) }).