// Package metrics is gin metrics library, collect five metrics, "uptime", "http_request_count_total", // "http_request_duration_seconds", "http_request_size_bytes", "http_response_size_bytes". package metrics import ( "net/http" "strconv" "time" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) var ( namespace = "gin" labels = []string{"status", "path", "method"} uptime = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, Name: "uptime", Help: "HTTP service uptime, updated every minute", }, nil, ) reqCount = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, Name: "http_request_count_total", Help: "Total number of HTTP requests made.", }, labels, ) reqDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: namespace, Name: "http_request_duration_seconds", Help: "HTTP request latencies in seconds.", }, labels, ) reqSizeBytes = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Namespace: namespace, Name: "http_request_size_bytes", Help: "HTTP request sizes in bytes.", }, labels, ) respSizeBytes = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Namespace: namespace, Name: "http_response_size_bytes", Help: "HTTP response sizes in bytes.", }, labels, ) ) // init registers the prometheus metrics func initPrometheus() { prometheus.MustRegister(uptime, reqCount, reqDuration, reqSizeBytes, respSizeBytes) go recordUptime() } // recordUptime increases service uptime per 1 minute. func recordUptime() { for range time.Tick(time.Minute) { uptime.WithLabelValues().Inc() } } // calcRequestSize returns the size of request object. func calcRequestSize(r *http.Request) float64 { size := 0 if r.URL != nil { size = len(r.URL.String()) } size += len(r.Method) size += len(r.Proto) for name, values := range r.Header { size += len(name) for _, value := range values { size += len(value) } } size += len(r.Host) // r.Form and r.MultipartForm are assumed to be included in r.URL. if r.ContentLength != -1 { size += int(r.ContentLength) } return float64(size) } // ------------------------------------------------------------------------------------------ // metricsHandler wrappers the standard http.Handler to gin.HandlerFunc func metricsHandler() gin.HandlerFunc { return func(c *gin.Context) { handler := promhttp.Handler() handler.ServeHTTP(c.Writer, c.Request) } } // Metrics returns a gin.HandlerFunc for exporting some Web metrics func Metrics(r *gin.Engine, opts ...Option) gin.HandlerFunc { o := defaultOptions() o.apply(opts...) // init prometheus initPrometheus() r.GET(o.metricsPath, metricsHandler()) return func(c *gin.Context) { start := time.Now() c.Next() ok := o.isIgnoreCodeStatus(c.Writer.Status()) || o.isIgnorePath(c.Request.URL.Path) || o.checkIgnoreMethod(c.Request.Method) if ok { return } // no response content will return -1 respSize := c.Writer.Size() if respSize < 0 { respSize = 0 } lvs := []string{strconv.Itoa(c.Writer.Status()), c.Request.URL.Path, c.Request.Method} reqCount.WithLabelValues(lvs...).Inc() reqDuration.WithLabelValues(lvs...).Observe(time.Since(start).Seconds()) reqSizeBytes.WithLabelValues(lvs...).Observe(calcRequestSize(c.Request)) respSizeBytes.WithLabelValues(lvs...).Observe(float64(respSize)) } }