-
Notifications
You must be signed in to change notification settings - Fork 151
🔥 feat: Prometheus middleware #1610
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
Draft
gaby
wants to merge
3
commits into
main
Choose a base branch
from
implement-v3-prometheus-contrib-module
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 ./... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ use ( | |
| ./v3/opa | ||
| ./v3/otel | ||
| ./v3/paseto | ||
| ./v3/prometheus | ||
| ./v3/sentry | ||
| ./v3/socketio | ||
| ./v3/swagger | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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). | ||
|
|
||
|  | ||
| [](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}, | ||
| })) | ||
gaby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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") | ||
| } | ||
| ``` | ||
gaby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### 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. | ||
gaby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ## 📊 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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...) | ||
| } | ||
gaby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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...) | ||
gaby marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return cfg | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.