mirror of https://github.com/zhufuyi/sponge
141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
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.",
|
|
}, 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 30 second.
|
|
func recordUptime() {
|
|
for range time.Tick(time.Second * 30) {
|
|
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))
|
|
}
|
|
}
|