|
@@ -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
|
|
|
+}
|