Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
31 changes: 31 additions & 0 deletions .github/workflows/test-prometheus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "Test Prometheus"

on:
push:
branches:
- main
paths:
- 'v3/prometheus/**/*.go'
- 'v3/prometheus/go.mod'
pull_request:
paths:
- 'v3/prometheus/**/*.go'
- 'v3/prometheus/go.mod'

jobs:
Tests:
runs-on: ubuntu-latest
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v5
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/prometheus
run: go test -v -race ./...
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Repository for third party middlewares and service implementations, with depende
* [loadshed](./v3/loadshed/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+loadshed%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-loadshed.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="loadshed workflow status" /> </a>
* [new relic](./v3/newrelic/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+newrelic%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-newrelic.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="new relic workflow status" /> </a>
* [monitor](./v3/monitor/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Monitor%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-monitor.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="monitor workflow status" /> </a>
* [prometheus](./v3/prometheus/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Prometheus%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-prometheus.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="prometheus workflow status" /> </a>
* [open policy agent](./v3/opa/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+opa%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-opa.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="OPA workflow status" /> </a>
* [otel (opentelemetry)](./v3/otel/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+otel%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-otel.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="otel workflow status" /> </a>
* [paseto](./v3/paseto/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+paseto%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-paseto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="paseto workflow status" /> </a>
Expand Down
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use (
./v3/opa
./v3/otel
./v3/paseto
./v3/prometheus
./v3/sentry
./v3/socketio
./v3/swagger
Expand Down
1 change: 1 addition & 0 deletions v3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Repository for third party middlewares and service implementations, with depende
* [loadshed](./loadshed/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+loadshed%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-loadshed.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="loadshed workflow status" /> </a>
* [new relic](./newrelic/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+newrelic%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-newrelic.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="new relic workflow status" /> </a>
* [monitor](./monitor/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Monitor%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-monitor.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="monitor workflow status" /> </a>
* [prometheus](./prometheus/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Prometheus%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-prometheus.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="prometheus workflow status" /> </a>
* [open policy agent](./opa/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+opa%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-opa.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="OPA workflow status" /> </a>
* [otel (opentelemetry)](./otel/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+otel%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-otel.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="otel workflow status" /> </a>
* [paseto](./paseto/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+paseto%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-paseto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="paseto workflow status" /> </a>
Expand Down
106 changes: 106 additions & 0 deletions v3/prometheus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Prometheus

Prometheus middleware for [Fiber v3](https://github.com/gofiber/fiber) based on [ansrivas/fiberprometheus](https://github.com/ansrivas/fiberprometheus).

![Release](https://img.shields.io/github/release/ansrivas/fiberprometheus.svg)
[![Discord](https://img.shields.io/badge/discord-join%20channel-7289DA)](https://gofiber.io/discord)

Following metrics are available by default:

```text
http_requests_total
http_requests_status_class_total
http_request_duration_seconds
http_requests_in_progress_total
http_request_size_bytes
http_response_size_bytes
```

`http_requests_in_progress_total` exposes both the HTTP method and normalized
route path so you can pinpoint which handlers are currently running.

> [!NOTE]
> The middleware requires Go 1.25 or newer and Fiber v3 (currently RC).

## 🚀 Installation

```bash
go get github.com/gofiber/contrib/v3/prometheus
```

## 📄 Example

```go
package main

import (
fiberprometheus "github.com/gofiber/contrib/v3/prometheus"
"github.com/gofiber/fiber/v3"
)

func main() {
app := fiber.New()

app.Use("/metrics", fiberprometheus.New(fiberprometheus.Config{
Service: "my-service-name",
SkipURIs: []string{"/ping"},
IgnoreStatusCodes: []int{401, 403, 404},
}))

app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello World")
})

app.Get("/ping", func(c fiber.Ctx) error {
return c.SendString("pong")
})

app.Post("/some", func(c fiber.Ctx) error {
return c.SendString("Welcome!")
})

app.Listen(":3000")
}
```

### Collector, OpenMetrics, and response options

The middleware exposes Prometheus collector toggles and `HandlerOpts` via
`Config`. By default it creates a private `Registerer`/`Gatherer` pair and uses
that for both registration and scraping. When customizing the registry, ensure
that the `Registerer` and `Gatherer` refer to the same metrics source (for
example, a `*prometheus.Registry`). Supplying only one that does not implement
the other interface or providing a mismatched pair will cause initialization to
panic so metrics are not silently dropped.

- `DisableGoCollector` disables the default Go runtime metrics collector when set to `true`.
- `DisableProcessCollector` disables the default process metrics collector when set to `true`.

- `EnableOpenMetrics` negotiates the experimental OpenMetrics encoding so exemplars are exported.
- `EnableOpenMetricsTextCreatedSamples` adds synthetic `_created` samples when OpenMetrics is enabled.
- `DisableCompression` disables gzip/zstd compression even when clients request it.

- `TrackUnmatchedRequests` records metrics for requests that miss all registered routes using `UnmatchedRouteLabel` as the path label. Defaults to `false`.
- `UnmatchedRouteLabel` customizes the path label applied to unmatched requests when tracking is enabled. Defaults to `/__unmatched__`.

- `RequestDurationBuckets`, `RequestSizeBuckets`, and `ResponseSizeBuckets` customize the histogram buckets used for latency and payload metrics. They default to:
- Duration: `[0.005 0.01 0.025 0.05 0.075 0.1 0.25 0.5 0.75 1 2.5 5 10 15 30 60]`
- Request size: `[256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 5242880]`
- Response size: `[256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 5242880]`

All of the options default to `false` and can be enabled or disabled individually as needed.

The metrics endpoint path is derived from how the middleware is mounted. In the
example above, calling `app.Use("/metrics", fiberprometheus.New(...))` exposes
the handler at `/metrics` while the middleware continues to instrument all
routed traffic.

## 📊 Result

- Hit the default url at http://localhost:3000
- Navigate to http://localhost:3000/metrics
- Metrics are recorded only for routes registered with Fiber unless `TrackUnmatchedRequests` is enabled, in which case unmatched requests are labeled with `UnmatchedRouteLabel`.

## 📈 Grafana Dashboard

- https://grafana.com/grafana/dashboards/14331
187 changes: 187 additions & 0 deletions v3/prometheus/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package prometheus

import (
"strings"

"github.com/gofiber/fiber/v3"
"github.com/prometheus/client_golang/prometheus"
)

// Config defines the middleware configuration.
type Config struct {
// Service is added as the `service` const label on every metric.
//
// Optional. Default: "" (label omitted).
Service string

// Namespace prefixes every metric name.
//
// Optional. Default: "http".
Namespace string

// Subsystem prefixes every metric name after Namespace.
//
// Optional. Default: "".
Subsystem string

// Labels are attached to every metric.
//
// Optional. Default: no labels.
Labels prometheus.Labels

// Registerer is used to register metrics.
//
// Optional. Default: a private registry.
Registerer prometheus.Registerer

// Gatherer provides metrics to the HTTP handler.
//
// Optional. Default: a private registry/gatherer pair created when neither
// Registerer nor Gatherer is supplied. If only one is provided, it must also
// implement the other interface or the middleware will panic to prevent
// silently omitting metrics.
Gatherer prometheus.Gatherer

// DisableGoCollector disables the Go runtime metrics collector registration.
//
// Optional. Default: false (collector enabled).
DisableGoCollector bool

// DisableProcessCollector disables the process metrics collector registration.
//
// Optional. Default: false (collector enabled).
DisableProcessCollector bool

// RequestDurationBuckets configures the histogram buckets used for request
// latency metrics. Provide nil to use the defaults.
//
// Optional. Default: []float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 10, 15, 30, 60}.
RequestDurationBuckets []float64

// RequestSizeBuckets configures the histogram buckets used for request
// payload size metrics. Provide nil to use the defaults.
//
// Optional. Default: []float64{256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 5242880}.
RequestSizeBuckets []float64

// ResponseSizeBuckets configures the histogram buckets used for response
// payload size metrics. Provide nil to use the defaults.
//
// Optional. Default: []float64{256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 5242880}.
ResponseSizeBuckets []float64

// TrackUnmatchedRequests toggles metrics for requests that do not resolve to a
// registered Fiber route.
//
// Optional. Default: false.
TrackUnmatchedRequests bool

// UnmatchedRouteLabel is the path label used when TrackUnmatchedRequests is
// enabled and a request does not match a registered route.
//
// Optional. Default: "/__unmatched__".
UnmatchedRouteLabel string

// EnableOpenMetrics exposes the experimental OpenMetrics encoding.
//
// Optional. Default: false.
EnableOpenMetrics bool

// EnableOpenMetricsTextCreatedSamples adds synthetic `_created` samples to
// OpenMetrics responses.
//
// Optional. Default: false.
EnableOpenMetricsTextCreatedSamples bool

// DisableCompression prevents gzip compression of metrics responses, even when
// requested by the client (both gzip and zstd).
//
// Optional. Default: false.
DisableCompression bool

// SkipURIs excludes matching routes from instrumentation.
//
// Optional. Default: none.
SkipURIs []string

// IgnoreStatusCodes excludes matching response status codes from metrics.
//
// Optional. Default: none.
IgnoreStatusCodes []int

// Next skips the middleware when it returns true.
//
// Optional. Default: nil.
Next func(fiber.Ctx) bool
}

var (
defaultRequestDurationBuckets = []float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 10, 15, 30, 60}
defaultRequestSizeBuckets = []float64{256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 5242880}
defaultResponseSizeBuckets = []float64{256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 5242880}
)

// ConfigDefault holds the default middleware configuration.
var ConfigDefault = Config{
Namespace: "http",
UnmatchedRouteLabel: "/__unmatched__",
RequestDurationBuckets: defaultRequestDurationBuckets,
RequestSizeBuckets: defaultRequestSizeBuckets,
ResponseSizeBuckets: defaultResponseSizeBuckets,
}

func configDefault(config ...Config) Config {
if len(config) == 0 {
cfg := ConfigDefault
cfg.Labels = make(prometheus.Labels)
cfg.RequestDurationBuckets = append([]float64(nil), ConfigDefault.RequestDurationBuckets...)
cfg.RequestSizeBuckets = append([]float64(nil), ConfigDefault.RequestSizeBuckets...)
cfg.ResponseSizeBuckets = append([]float64(nil), ConfigDefault.ResponseSizeBuckets...)
return cfg
}

cfg := config[0]

if cfg.Namespace == "" {
cfg.Namespace = ConfigDefault.Namespace
}

if cfg.UnmatchedRouteLabel == "" {
cfg.UnmatchedRouteLabel = ConfigDefault.UnmatchedRouteLabel
} else {
cfg.UnmatchedRouteLabel = strings.Clone(cfg.UnmatchedRouteLabel)
}

if cfg.RequestDurationBuckets == nil {
cfg.RequestDurationBuckets = append([]float64(nil), ConfigDefault.RequestDurationBuckets...)
} else {
cfg.RequestDurationBuckets = append([]float64(nil), cfg.RequestDurationBuckets...)
}

if cfg.RequestSizeBuckets == nil {
cfg.RequestSizeBuckets = append([]float64(nil), ConfigDefault.RequestSizeBuckets...)
} else {
cfg.RequestSizeBuckets = append([]float64(nil), cfg.RequestSizeBuckets...)
}

if cfg.ResponseSizeBuckets == nil {
cfg.ResponseSizeBuckets = append([]float64(nil), ConfigDefault.ResponseSizeBuckets...)
} else {
cfg.ResponseSizeBuckets = append([]float64(nil), cfg.ResponseSizeBuckets...)
}

if cfg.Labels == nil {
cfg.Labels = make(prometheus.Labels)
} else {
labels := make(prometheus.Labels, len(cfg.Labels))
for key, value := range cfg.Labels {
labels[key] = value
}
cfg.Labels = labels
}

cfg.SkipURIs = append([]string(nil), cfg.SkipURIs...)
cfg.IgnoreStatusCodes = append([]int(nil), cfg.IgnoreStatusCodes...)

return cfg
}
41 changes: 41 additions & 0 deletions v3/prometheus/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module github.com/gofiber/contrib/v3/prometheus

go 1.25.0

require (
github.com/gofiber/fiber/v3 v3.0.0-rc.3
github.com/gofiber/utils/v2 v2.0.0-rc.3
github.com/prometheus/client_golang v1.23.2
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/sdk v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
)

require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gofiber/schema v1.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/tinylib/msgp v1.5.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.68.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
)
Loading
Loading