package middlewares
import (
"fmt"
"net/http"
"regexp"
"sync"
"time"
_ "git.icarephone.com/icf/com.git/logger"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
)
/*
* 指标监控处理中间件
————————————————————————
*/
const namespace = "service"
var (
labels = []string{"status", "endpoint", "method"}
requestHandlerPool sync.Pool
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 request sizes in bytes.",
}, labels,
)
)
// init registers the prometheus metrics
func init() {
prometheus.MustRegister(uptime, reqCount, reqDuration, reqSizeBytes, respSizeBytes)
go recordUptime()
}
// recordUptime increases service uptime per second.
func recordUptime() {
for range time.Tick(time.Second) {
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)
}
// PromOpts represents the Prometheus middleware Options.
// It is used for filtering labels by regex.
type PromOpts struct {
ExcludeRegexStatus string
ExcludeRegexEndpoint string
ExcludeRegexMethod string
}
var defaultPromOpts = &PromOpts{}
func acquireRequestFromPool() *fasthttp.Request {
rp := requestHandlerPool.Get()
if rp == nil {
return new(fasthttp.Request)
}
frc := rp.(*fasthttp.Request)
return frc
}
// Idea is from https://github.com/DanielHeckrath/gin-prometheus/blob/master/gin_prometheus.go and https://github.com/zsais/go-gin-prometheus/blob/master/middleware.go
func computeApproximateRequestSize(ctx *fasthttp.Request, out chan int) {
s := 0
if ctx.URI() != nil {
s += len(ctx.URI().Path())
s += len(ctx.URI().Host())
}
s += len(ctx.Header.Method())
s += len("HTTP/1.1")
ctx.Header.VisitAll(func(key, value []byte) {
if string(key) != "Host" {
s += len(key) + len(value)
}
})
if ctx.Header.ContentLength() != -1 {
s += ctx.Header.ContentLength()
}
out <- s
}
// checkLabel returns the match result of labels.
// Return true if regex-pattern compiles failed.
func (po *PromOpts) checkLabel(label, pattern string) bool {
if pattern == "" {
return true
}
matched, err := regexp.MatchString(pattern, label)
if err != nil {
return true
}
return !matched
}
func PROM(next fasthttp.RequestHandler) fasthttp.RequestHandler {
promOpts := defaultPromOpts
return fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) {
reqSize := make(chan int)
frc := acquireRequestFromPool()
ctx.Request.CopyTo(frc)
go computeApproximateRequestSize(frc, reqSize)
start := time.Now()
next(ctx)
status := fmt.Sprintf("%d", ctx.Response.StatusCode())
endpoint := string(ctx.Request.URI().Path())
method := string(ctx.Method())
lvs := []string{status, endpoint, method}
isOk := promOpts.checkLabel(status, promOpts.ExcludeRegexStatus) &&
promOpts.checkLabel(endpoint, promOpts.ExcludeRegexEndpoint) &&
promOpts.checkLabel(method, promOpts.ExcludeRegexMethod)
if !isOk {
return
}
reqCount.WithLabelValues(lvs...).Inc()
reqDuration.WithLabelValues(lvs...).Observe(time.Since(start).Seconds())
reqSizeBytes.WithLabelValues(lvs...).Observe(float64(<-reqSize))
respSizeBytes.WithLabelValues(lvs...).Observe(float64(len(ctx.Response.Body())))
})
}
// PromHandler wrappers the standard http.Handler to gin.HandlerFunc
func PromHandler() fasthttp.RequestHandler {
return fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler())
}