diff --git a/options.go b/options.go index 4078350..b193d1c 100644 --- a/options.go +++ b/options.go @@ -124,6 +124,19 @@ func HandlerNameFunc(f func(c *gin.Context) string) PrometheusOption { } } +// HostFunc is an option allowing to set the HostFunc with New. +// Use this option if you want to override the default behavior (i.e. using +// c.Request.Host). This is useful to reduce metric cardinality when the Host +// header varies (e.g. dynamic subdomains) but requests are handled the same. +// Example: +// r := gin.Default() +// p := ginprom.New(HostFunc(func(c *gin.Context) string { return "my-service" })) +func HostFunc(f func(c *gin.Context) string) PrometheusOption { + return func(p *Prometheus) { + p.HostFunc = f + } +} + // HandlerOpts is an option allowing to set the promhttp.HandlerOpts. // Use this option if you want to override the default zero value. func HandlerOpts(opts promhttp.HandlerOpts) PrometheusOption { diff --git a/prom.go b/prom.go index b8713bc..cbba91b 100644 --- a/prom.go +++ b/prom.go @@ -21,6 +21,7 @@ var defaultNs = "gin" var defaultSys = "gonic" var defaultHandlerNameFunc = (*gin.Context).HandlerName var defaultRequestPathFunc = (*gin.Context).FullPath +var defaultHostFunc = func(c *gin.Context) string { return c.Request.Host } var defaultReqCntMetricName = "requests_total" var defaultReqDurMetricName = "request_duration" @@ -79,6 +80,7 @@ type Prometheus struct { Registry *prometheus.Registry HandlerNameFunc func(c *gin.Context) string RequestPathFunc func(c *gin.Context) string + HostFunc func(c *gin.Context) string HandlerOpts promhttp.HandlerOpts NativeHistogramBucketFactor float64 @@ -272,6 +274,7 @@ func New(options ...PrometheusOption) *Prometheus { Subsystem: defaultSys, HandlerNameFunc: defaultHandlerNameFunc, RequestPathFunc: defaultRequestPathFunc, + HostFunc: defaultHostFunc, RequestCounterMetricName: defaultReqCntMetricName, RequestDurationMetricName: defaultReqDurMetricName, RequestSizeMetricName: defaultReqSzMetricName, @@ -392,7 +395,8 @@ func (p *Prometheus) Instrument() gin.HandlerFunc { elapsed := float64(time.Since(start)) / float64(time.Second) resSz := float64(c.Writer.Size()) - labels := []string{status, c.Request.Method, p.HandlerNameFunc(c), c.Request.Host, path} + host := p.HostFunc(c) + labels := []string{status, c.Request.Method, p.HandlerNameFunc(c), host, path} if p.customCounterLabelsProvider != nil { extraLabels := p.customCounterLabelsProvider(c) for _, label := range p.customCounterLabels { @@ -401,7 +405,7 @@ func (p *Prometheus) Instrument() gin.HandlerFunc { } p.reqCnt.WithLabelValues(labels...).Inc() - p.reqDur.WithLabelValues(c.Request.Method, path, c.Request.Host).Observe(elapsed) + p.reqDur.WithLabelValues(c.Request.Method, path, host).Observe(elapsed) p.reqSz.Observe(float64(reqSz)) p.resSz.Observe(resSz) } diff --git a/prom_test.go b/prom_test.go index 1d9dbb2..627a5e4 100644 --- a/prom_test.go +++ b/prom_test.go @@ -124,6 +124,38 @@ func TestHandlerNameFunc(t *testing.T) { }) } +func TestHostFunc(t *testing.T) { + r := gin.New() + registry := prometheus.NewRegistry() + host := "normalized.example.com" + lhost := fmt.Sprintf("host=%q", host) + + p := New( + HostFunc(func(c *gin.Context) string { + return host + }), + Registry(registry), + Engine(r), + ) + + r.Use(p.Instrument()) + + r.GET("/", func(context *gin.Context) { + context.Status(http.StatusOK) + }) + + g := gofight.New() + + g.GET("/").Run(r, func(response gofight.HTTPResponse, request gofight.HTTPRequest) { + assert.Equal(t, response.Code, http.StatusOK) + }) + + g.GET(p.MetricsPath).Run(r, func(response gofight.HTTPResponse, request gofight.HTTPRequest) { + assert.Equal(t, response.Code, http.StatusOK) + assert.Contains(t, response.Body.String(), lhost) + }) +} + func TestHandlerOpts(t *testing.T) { r := gin.New() registry := prometheus.NewRegistry()