Gin集成Prometheus通过pushgateway或直接上报监控指标至prometheus

417 阅读2分钟

业务指标上报metrics到prometheus,已经是基操了

实现方式有两种:

  1. 推送到pushgateway,由prometheus 过来pull pushgateway,进而展示在如grafana
  2. 启动metrics接口;注入Gin中间件,由prometheus 过来pull metrics指标,进而展示在如grafana

将metrics监控指标推送至 pushgateway

package prometheus

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/push"
)

var (
    HttpReqDurationHistogram = promauto.NewHistogramVec(prometheus.HistogramOpts{
        Name:    "p_saplat_http_request_duration_seconds",
        Help:    "Histogram of response latency (seconds) of http responses.",
        Buckets: prometheus.LinearBuckets(0.1, 0.1, 10),
    }, []string{"route1", "method1", "status_code1"})

    TotalRequestsCounter = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "p_saplat_http_requests_total",
        Help: "Total number of HTTP requests",
    }, []string{"route2", "method2", "status_code2"})
)

type PrometheusPusher struct {
    pusher *push.Pusher
}

func NewPrometheusPusher(pushGatewayURL, jobName string, labels map[string]string) *PrometheusPusher {
    pusher := push.New(pushGatewayURL, jobName)
    for key, value := range labels {
        pusher = pusher.Grouping(key, value)
    }
    return &PrometheusPusher{pusher: pusher}
}

func (p *PrometheusPusher) Push() error {
    return p.pusher.Push()
}

func (p *PrometheusPusher) AddCollector(collector prometheus.Collector) {
    p.pusher.Collector(collector)
}

中间件

package middleware

import (
	"fmt"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	// "github.com/prometheus/client_golang/prometheus/push"
	"x.com/saplat/conf"
	"x.com/saplat/service/prometheus"
)

func PrometheusPushGatewayMiddleware(pushGatewayURL string) gin.HandlerFunc {
    // 静态配置推送标签,可以根据需要在这里添加标签。
    promPusher := prometheus.NewPrometheusPusher(pushGatewayURL, "gin_service", map[string]string{
        // 如果有全局的标签可以在这里添加
        // "label1": "value1",
    })

    // 如果collector是动态创建的,则可以在接下来注册收集器
    // 但在大多数时候我们的collector是预注册的全局变量。
    return func(c *gin.Context) {
        start := time.Now()

        c.Next()

        duration := time.Since(start)
        path := c.Request.URL.Path
        method := c.Request.Method
        status := strconv.Itoa(c.Writer.Status())

        // 注册收集器
        promPusher.AddCollector(prometheus.HttpReqDurationHistogram)
        promPusher.AddCollector(prometheus.TotalRequestsCounter)

        // 更新指标
        prometheus.HttpReqDurationHistogram.WithLabelValues(path, method, status).Observe(duration.Seconds())
        prometheus.TotalRequestsCounter.WithLabelValues(path, method, status).Inc()

        // 推送数据
        if err := promPusher.Push(); err != nil {
            // 处理这里的错误
            fmt.Printf("Could not push metrics to Pushgateway: %v", err)
        } else {
            fmt.Println("Pushed metrics to Pushgateway")
        }
    }
}

调用

	// Prometheus Push
	r.Use(middleware.PrometheusPushGatewayMiddleware(conf.PushGatewayURL))

在pushteway 确认上报情况

推送metrics监控指标至prometheus

package prometheus

import (
    "fmt"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "x.com/saplat/conf"
)

var AppID = conf.AppID

// 请求计数
var TotalRequests = promauto.NewCounterVec(
    prometheus.CounterOpts{
        Name: fmt.Sprintf("%v_http_requests_total", AppID),
        Help: fmt.Sprintf("%v Total number of HTTP requests", AppID),
    },
    []string{"method", "endpoint", "status_code","AppID"},
)

// 请求响应时间
var RequestDuration = promauto.NewSummaryVec(
    prometheus.SummaryOpts{
        Name:       fmt.Sprintf("%v_http_request_duration_seconds", AppID),
        Help:       fmt.Sprintf("%v Summary of the response latency (seconds) of HTTP requests", AppID),
        Objectives: map[float64]float64{0.95: 0.01, 0.99: 0.01}, // Objectives定义了我们需要的分位数
    },
    []string{"method", "endpoint","AppID"},
)

var TotalRequestsByStatus = promauto.NewCounterVec(
    prometheus.CounterOpts{
        Name: fmt.Sprintf("%v_http_requests_total_by_status", AppID),
        Help: fmt.Sprintf("%v Total number of HTTP requests by status code", AppID),
    },
    []string{"status_code","AppID"},
)

中间件

package middleware

import (
	"fmt"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	// "github.com/prometheus/client_golang/prometheus/push"
	"x.com/saplat/conf"
	"x.com/saplat/service/prometheus"
)

func PrometheusMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        path := c.Request.URL.Path
        method := c.Request.Method
        status := strconv.Itoa(c.Writer.Status())

        // 请求开始之前的时间
        start := time.Now()

        c.Next() // 处理请求

        // 请求处理完成后统计响应时间
        duration := time.Since(start)
        statusCode := strconv.Itoa(c.Writer.Status())

        // 收集指标信息
        prometheus.TotalRequests.WithLabelValues(method, path, status,conf.AppID).Inc()                 // 增加请求计数
        prometheus.RequestDuration.WithLabelValues(method, path,conf.AppID).Observe(duration.Seconds()) // 统计请求响应时间
        prometheus.TotalRequestsByStatus.WithLabelValues(statusCode,conf.AppID).Inc()                   // 增加对应状态码的计数
    }
}

调用

注意,如果多个Gin中间件,需要将r.Use 前置

	r.GET("/metrics", gin.WrapH(promhttp.Handler()))
	r.Use(middleware.PrometheusMiddleware())

确认/metrics业务指标是否注入

效果

image.png