Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for custom labels in the counter metric #62

Merged
merged 4 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Inspired by [github.com/zsais/go-gin-prometheus](https://github.com/zsais/go-gin
- [Subsystem](#subsystem)
- [Engine](#engine)
- [Prometheus Registry](#prometheus-registry)
- [HandlerNameFunc](#handlernamefunc)
- [RequestPathFunc](#requestpathfunc)
- [CustomCounterLabels](#customcounterlabels)
- [Ignore](#ignore)
- [Token](#token)
- [Bucket size](#bucket-size)
Expand Down Expand Up @@ -229,6 +232,24 @@ p := ginprom.New(
r.Use(p.Instrument())
```

### CustomCounterLabels

Add custom labels to the counter metric.

```go
r := gin.Default()
p := ginprom.New(
ginprom.CustomCounterLabels([]string{"client_id"}, func(c *gin.Context) map[string]string {
client_id := c.GetHeader("x-client-id")
if client_id == "" {
client_id = "unknown"
}
return map[string]string{"client_id": client_id}
}),
)
r.Use(p.Instrument())
```

### Ignore

Ignore allows to completely ignore some routes. Even though you can apply the
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/Depado/ginprom
module github.com/sralloza/ginprom
sralloza marked this conversation as resolved.
Show resolved Hide resolved

go 1.20

Expand Down
7 changes: 7 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,10 @@ func RequestPathFunc(f func(c *gin.Context) string) PrometheusOption {
p.RequestPathFunc = f
}
}

func CustomCounterLabels(labels []string, f func(c *gin.Context) map[string]string) PrometheusOption {
return func(p *Prometheus) {
p.customCounterLabelsProvider = f
p.customCounterLabels = labels
}
}
19 changes: 15 additions & 4 deletions prom.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ type Prometheus struct {
reqDur *prometheus.HistogramVec
reqSz, resSz prometheus.Summary

customGauges pmapGauge
customCounters pmapCounter
customGauges pmapGauge
customCounters pmapCounter
customCounterLabelsProvider func(c *gin.Context) map[string]string
customCounterLabels []string

MetricsPath string
Namespace string
Expand Down Expand Up @@ -222,6 +224,7 @@ func New(options ...PrometheusOption) *Prometheus {
}
p.customGauges.values = make(map[string]prometheus.GaugeVec)
p.customCounters.values = make(map[string]prometheus.CounterVec)
p.customCounterLabels = make([]string, 0)

p.Ignored.values = make(map[string]bool)
for _, option := range options {
Expand Down Expand Up @@ -251,7 +254,7 @@ func (p *Prometheus) register() {
Name: p.RequestCounterMetricName,
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
},
[]string{"code", "method", "handler", "host", "path"},
append([]string{"code", "method", "handler", "host", "path"}, p.customCounterLabels...),
)
p.mustRegister(p.reqCnt)

Expand Down Expand Up @@ -312,7 +315,15 @@ func (p *Prometheus) Instrument() gin.HandlerFunc {
elapsed := float64(time.Since(start)) / float64(time.Second)
resSz := float64(c.Writer.Size())

p.reqCnt.WithLabelValues(status, c.Request.Method, p.HandlerNameFunc(c), c.Request.Host, path).Inc()
labels := []string{status, c.Request.Method, p.HandlerNameFunc(c), c.Request.Host, path}
if p.customCounterLabelsProvider != nil {
extraLabels := p.customCounterLabelsProvider(c)
for _, label := range p.customCounterLabels {
labels = append(labels, extraLabels[label])
}
}

p.reqCnt.WithLabelValues(labels...).Inc()
p.reqDur.WithLabelValues(c.Request.Method, path, c.Request.Host).Observe(elapsed)
p.reqSz.Observe(float64(reqSz))
p.resSz.Observe(resSz)
Expand Down
58 changes: 58 additions & 0 deletions prom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,64 @@ func TestEmptyRouter(t *testing.T) {
unregister(p)
}

func TestCustomCounterMetrics(t *testing.T) {
r := gin.New()
p := New(Engine(r), CustomCounterLabels([]string{"client_id", "tenant_id"}, func(c *gin.Context) map[string]string {
sralloza marked this conversation as resolved.
Show resolved Hide resolved
clientId := c.GetHeader("X-Client-ID")
if clientId == "" {
clientId = "unknown"
}
tenantId := c.GetHeader("X-Tenant-ID")
if tenantId == "" {
tenantId = "unknown"
}
return map[string]string{
"client_id": clientId,
"tenant_id": tenantId,
}
}))
r.Use(p.Instrument())

r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})

g := gofight.New()
g.GET(p.MetricsPath).Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Equal(t, http.StatusOK, r.Code)
assert.NotContains(t, r.Body.String(), prometheus.BuildFQName(p.Namespace, p.Subsystem, "requests_total"))
assert.NotContains(t, r.Body.String(), "client_id")
assert.NotContains(t, r.Body.String(), "tenant_id")
})

g.GET("/ping").Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) })

g.GET(p.MetricsPath).Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
body := r.Body.String()
assert.Equal(t, http.StatusOK, r.Code)
assert.Contains(t, body, prometheus.BuildFQName(p.Namespace, p.Subsystem, "requests_total"))
assert.Contains(t, r.Body.String(), "client_id=\"unknown\"")
assert.Contains(t, r.Body.String(), "tenant_id=\"unknown\"")
assert.NotContains(t, r.Body.String(), "client_id=\"client-id\"")
assert.NotContains(t, r.Body.String(), "tenant_id=\"tenant-id\"")
})

g.GET("/ping").
SetHeader(gofight.H{"X-Client-Id": "client-id", "X-Tenant-Id": "tenant-id"}).
Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { assert.Equal(t, http.StatusOK, r.Code) })

g.GET(p.MetricsPath).Run(r, func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
body := r.Body.String()
assert.Equal(t, http.StatusOK, r.Code)
assert.Contains(t, body, prometheus.BuildFQName(p.Namespace, p.Subsystem, "requests_total"))
assert.Contains(t, r.Body.String(), "client_id=\"unknown\"")
assert.Contains(t, r.Body.String(), "tenant_id=\"unknown\"")
assert.Contains(t, r.Body.String(), "client_id=\"client-id\"")
assert.Contains(t, r.Body.String(), "tenant_id=\"tenant-id\"")
})
unregister(p)
}

func TestIgnore(t *testing.T) {
r := gin.New()
ipath := "/ping"
Expand Down