Kaynağa Gözat

feat: 增加prometheus支持

lijian 2 yıl önce
ebeveyn
işleme
acd14c6383

+ 6 - 1
boot/boot.go

@@ -42,6 +42,10 @@ func Init(ctx context.Context) func() {
 	s := g.Server()
 	// 每个请求生成新的追踪Id,如果上下文件中没有trace-id
 	s.Use(middleware.TraceIdMiddleware())
+	if g.Cfg().GetBool("prometheus.enable") {
+		p := middleware.NewPrometheus("ghttp")
+		p.Use(s)
+	}
 	// 统一处理内部错误
 	s.Use(func(r *ghttp.Request) {
 		r.Middleware.Next()
@@ -58,6 +62,7 @@ func Init(ctx context.Context) func() {
 	}
 
 }
+
 // 初始化jwt认证,可以把相关配置放到config.toml中
 func initJwtAuth(ctx context.Context, container *dig.Container) error {
 	var opts []auth.Option
@@ -78,7 +83,7 @@ func initJwtAuth(ctx context.Context, container *dig.Container) error {
 	case "HS512":
 		opts = append(opts, auth.SetSigningMethod(jwt.SigningMethodHS512))
 	}
-	return container.Provide(func() auth.Auther { return auth.New(opts...)})
+	return container.Provide(func() auth.Auther { return auth.New(opts...) })
 }
 
 // 初始化redis

+ 11 - 9
config/config.toml

@@ -36,12 +36,14 @@
     CtxKeys = ["user_id", "trace_id", "span_title", "span_function", "version"]
 # 请求频率限制(需要启用redis配置)
 [rate_limiter]
-    # 是否启用
-    enable = true
-    # 每分钟每个用户允许的最大请求数量
-    count = 10
-    # redis数据库(如果存储方式是redis,则指定存储的数据库)
-    redis_db = 10
+# 是否启用
+enable = true
+# 每分钟每个用户允许的最大请求数量
+count = 10
+# redis数据库(如果存储方式是redis,则指定存储的数据库)
+redis_db = 10
+[prometheus]
+enable = true
 # 跨域请求
 [cors]
     # 是否启用
@@ -69,15 +71,15 @@
 # mysql数据库配置
 [mysql]
     # 连接地址
-    host = "39.98.250.155"
+host = "192.168.0.224"
     # 连接端口
     port= 3306
     # 用户名
     user = "root"
     # 密码
-    password = "gEkYDPloQcp93t4WHr3X"
+password = "zJv4DwFL6G2MgSvP@"
     # 数据库
-    db_name = "gxt-release"
+db_name = "file-server"
     # 连接参数
     parameters = "charset=utf8mb4&parseTime=True&loc=Local&allowNativePasswords=true"
 # gorm配置

+ 9 - 2
go.mod

@@ -1,11 +1,18 @@
 module gxt-api-frame
 
 require (
+	github.com/BurntSushi/toml v1.1.0 // indirect
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
-	github.com/go-redis/redis/v8 v8.3.3
+	github.com/fatih/color v1.13.0 // indirect
+	github.com/fsnotify/fsnotify v1.5.4 // indirect
+	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-redis/redis_rate/v9 v9.0.2
-	github.com/gogf/gf v1.14.2
+	github.com/gogf/gf v1.16.9
+	github.com/gorilla/websocket v1.5.0 // indirect
+	github.com/prometheus/client_golang v1.13.0
+	go.opentelemetry.io/otel v1.7.0 // indirect
 	go.uber.org/dig v1.10.0
+	golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 // indirect
 	gorm.io/driver/mysql v1.0.3
 	gorm.io/gorm v1.20.6
 )

+ 1 - 0
library/middleware/mw_rate_limiter.go

@@ -21,6 +21,7 @@ func RateLimiterMiddleware(skippers ...SkipperFunc) ghttp.HandlerFunc {
 		return func(r *ghttp.Request) {
 			logger.Warnf(gplus.NewContext(r), "限流中间件无法正常使用,请启用redis配置[redis.enable]")
 			r.Middleware.Next()
+			return
 		}
 	}
 

+ 282 - 0
library/middleware/prometheus.go

@@ -0,0 +1,282 @@
+package middleware
+
+import (
+	"context"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
+	"gxt-api-frame/library/logger"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"time"
+)
+
+var defaultMetricPath = "/metrics"
+
+var reqCnt = &Metric{
+	ID:          "reqCnt",
+	Name:        "requests_total",
+	Description: "How many HTTP requests processed, partitioned by status code and HTTP method.",
+	Type:        "counter_vec",
+	Args:        []string{"code", "method", "host", "url"}}
+
+var reqDur = &Metric{
+	ID:          "reqDur",
+	Name:        "request_duration_seconds",
+	Description: "The HTTP request latencies in seconds.",
+	Type:        "histogram_vec",
+	Args:        []string{"code", "method", "url"},
+}
+
+var resSz = &Metric{
+	ID:          "resSz",
+	Name:        "response_size_bytes",
+	Description: "The HTTP response sizes in bytes.",
+	Type:        "summary"}
+
+var reqSz = &Metric{
+	ID:          "reqSz",
+	Name:        "request_size_bytes",
+	Description: "The HTTP request sizes in bytes.",
+	Type:        "summary"}
+
+var standardMetrics = []*Metric{
+	reqCnt,
+	reqDur,
+	resSz,
+	reqSz,
+}
+
+type Metric struct {
+	MetricCollector prometheus.Collector
+	ID              string
+	Name            string
+	Description     string
+	Type            string
+	Args            []string
+}
+type RequestCounterURLLabelMappingFn func(c *ghttp.Request) string
+
+type Prometheus struct {
+	reqCnt        *prometheus.CounterVec
+	reqDur        *prometheus.HistogramVec
+	reqSz, resSz  prometheus.Summary
+	router        *ghttp.Server
+	listenAddress string
+	Ppg           PrometheusPushGateway
+
+	MetricsList []*Metric
+	MetricsPath string
+
+	ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn
+
+	// gin.Context string to use as a prometheus URL label
+	URLLabelFromContext string
+}
+type PrometheusPushGateway struct {
+	PushIntervalSeconds time.Duration
+	PushGatewayURL      string
+	MetricsURL          string
+	Job                 string
+}
+
+// NewPrometheus generates a new set of metrics with a certain subsystem name
+func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus {
+
+	var metricsList []*Metric
+
+	if len(customMetricsList) > 1 {
+		panic("Too many args. NewPrometheus( string, <optional []*Metric> ).")
+	} else if len(customMetricsList) == 1 {
+		metricsList = customMetricsList[0]
+	}
+
+	for _, metric := range standardMetrics {
+		metricsList = append(metricsList, metric)
+	}
+
+	p := &Prometheus{
+		MetricsList: metricsList,
+		MetricsPath: defaultMetricPath,
+		ReqCntURLLabelMappingFn: func(c *ghttp.Request) string {
+			return c.Request.URL.Path
+		},
+	}
+
+	p.registerMetrics(subsystem)
+
+	return p
+}
+
+// Use adds the middleware to a gin engine.
+func (p *Prometheus) Use(e *ghttp.Server) {
+	e.Use(p.HandlerFunc())
+	p.SetMetricsPath(e)
+}
+
+func (p *Prometheus) getMetrics() []byte {
+	response, _ := http.Get(p.Ppg.MetricsURL)
+	defer response.Body.Close()
+	body, _ := ioutil.ReadAll(response.Body)
+	return body
+}
+
+// NewMetric associates prometheus.Collector based on Metric.Type
+func NewMetric(m *Metric, subsystem string) prometheus.Collector {
+	var metric prometheus.Collector
+	switch m.Type {
+	case "counter_vec":
+		metric = prometheus.NewCounterVec(
+			prometheus.CounterOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+			m.Args,
+		)
+	case "counter":
+		metric = prometheus.NewCounter(
+			prometheus.CounterOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+		)
+	case "gauge_vec":
+		metric = prometheus.NewGaugeVec(
+			prometheus.GaugeOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+			m.Args,
+		)
+	case "gauge":
+		metric = prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+		)
+	case "histogram_vec":
+		metric = prometheus.NewHistogramVec(
+			prometheus.HistogramOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+			m.Args,
+		)
+	case "histogram":
+		metric = prometheus.NewHistogram(
+			prometheus.HistogramOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+		)
+	case "summary_vec":
+		metric = prometheus.NewSummaryVec(
+			prometheus.SummaryOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+			m.Args,
+		)
+	case "summary":
+		metric = prometheus.NewSummary(
+			prometheus.SummaryOpts{
+				Subsystem: subsystem,
+				Name:      m.Name,
+				Help:      m.Description,
+			},
+		)
+	}
+	return metric
+}
+func (p *Prometheus) registerMetrics(subsystem string) {
+
+	for _, metricDef := range p.MetricsList {
+		metric := NewMetric(metricDef, subsystem)
+		if err := prometheus.Register(metric); err != nil {
+			logger.Errorf(context.Background(), "%s could not be registered in Prometheus", metricDef.Name)
+		}
+		switch metricDef {
+		case reqCnt:
+			p.reqCnt = metric.(*prometheus.CounterVec)
+		case reqDur:
+			p.reqDur = metric.(*prometheus.HistogramVec)
+		case resSz:
+			p.resSz = metric.(prometheus.Summary)
+		case reqSz:
+			p.reqSz = metric.(prometheus.Summary)
+		}
+		metricDef.MetricCollector = metric
+	}
+}
+
+// SetMetricsPath set metrics paths
+func (p *Prometheus) SetMetricsPath(s *ghttp.Server) {
+	s.BindHandler(p.MetricsPath, prometheusHandler())
+}
+
+func prometheusHandler() ghttp.HandlerFunc {
+	h := promhttp.Handler()
+	return func(c *ghttp.Request) {
+		h.ServeHTTP(c.Response.Writer, c.Request)
+	}
+}
+
+// HandlerFunc defines handler function for middleware
+func (p *Prometheus) HandlerFunc() ghttp.HandlerFunc {
+	return func(c *ghttp.Request) {
+
+		if c.Request.URL.Path == p.MetricsPath || c.Request.URL.Path == "/favicon.ico" {
+			c.Middleware.Next()
+			return
+		}
+
+		start := time.Now()
+		reqSz := computeApproximateRequestSize(c.Request)
+
+		c.Middleware.Next()
+		status := strconv.Itoa(c.Response.Status)
+		elapsed := float64(time.Since(start)) / float64(time.Second)
+		resSz := float64(c.Response.BufferLength())
+
+		url := p.ReqCntURLLabelMappingFn(c)
+		if len(p.URLLabelFromContext) > 0 {
+			u := c.Get(p.URLLabelFromContext)
+			url = u.(string)
+		}
+		p.reqDur.WithLabelValues(status, c.Request.Method, url).Observe(elapsed)
+		p.reqCnt.WithLabelValues(status, c.Request.Method, c.Request.Host, url).Inc()
+		p.reqSz.Observe(float64(reqSz))
+		p.resSz.Observe(resSz)
+	}
+}
+
+func computeApproximateRequestSize(r *http.Request) int {
+	s := 0
+	if r.URL != nil {
+		s = len(r.URL.Path)
+	}
+
+	s += len(r.Method)
+	s += len(r.Proto)
+	for name, values := range r.Header {
+		s += len(name)
+		for _, value := range values {
+			s += len(value)
+		}
+	}
+	s += len(r.Host)
+
+	if r.ContentLength != -1 {
+		s += int(r.ContentLength)
+	}
+	return s
+}