[chore/bugfix] Fix double gzip on prometheus endpoint (#2383)

* [chore] Move "/metrics" into separate API module

* use our own gzip middleware for prom
This commit is contained in:
tobi 2023-11-23 19:10:51 +01:00 committed by GitHub
parent 2033915aaf
commit 2b9cf56f56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 22 deletions

View file

@ -293,6 +293,7 @@ func(context.Context, time.Time) {
var (
authModule = api.NewAuth(dbService, processor, idp, routerSession, sessionName) // auth/oauth paths
clientModule = api.NewClient(dbService, processor) // api client endpoints
metricsModule = api.NewMetrics() // Metrics endpoints
fileserverModule = api.NewFileserver(processor) // fileserver endpoints
wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints
nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint
@ -322,6 +323,7 @@ func(context.Context, time.Time) {
// apply throttling *after* rate limiting
authModule.Route(router, clLimit, clThrottle, gzip)
clientModule.Route(router, clLimit, clThrottle, gzip)
metricsModule.Route(router, clLimit, clThrottle, gzip)
fileserverModule.Route(router, fsLimit, fsThrottle)
wellKnownModule.Route(router, gzip, s2sLimit, s2sThrottle)
nodeInfoModule.Route(router, s2sLimit, s2sThrottle, gzip)

View file

@ -212,6 +212,7 @@
var (
authModule = api.NewAuth(state.DB, processor, idp, routerSession, sessionName) // auth/oauth paths
clientModule = api.NewClient(state.DB, processor) // api client endpoints
metricsModule = api.NewMetrics() // Metrics endpoints
fileserverModule = api.NewFileserver(processor) // fileserver endpoints
wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints
nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint
@ -222,6 +223,7 @@
// these should be routed in order
authModule.Route(router)
clientModule.Route(router)
metricsModule.Route(router)
fileserverModule.Route(router)
wellKnownModule.Route(router)
nodeInfoModule.Route(router)

66
internal/api/metrics.go Normal file
View file

@ -0,0 +1,66 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package api
import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/api/metrics"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/middleware"
"github.com/superseriousbusiness/gotosocial/internal/router"
)
type Metrics struct {
metrics *metrics.Module
}
func (mt *Metrics) Route(r *router.Router, m ...gin.HandlerFunc) {
if !config.GetMetricsEnabled() {
// Noop: metrics
// not enabled.
return
}
// Create new group on top level "metrics" prefix.
metricsGroup := r.AttachGroup("metrics")
metricsGroup.Use(m...)
metricsGroup.Use(
middleware.CacheControl(middleware.CacheControlConfig{
// Never cache metrics responses.
Directives: []string{"no-store"},
}),
)
// Attach basic auth if enabled.
if config.GetMetricsAuthEnabled() {
var (
username = config.GetMetricsAuthUsername()
password = config.GetMetricsAuthPassword()
accounts = gin.Accounts{username: password}
)
metricsGroup.Use(gin.BasicAuth(accounts))
}
mt.metrics.Route(metricsGroup.Handle)
}
func NewMetrics() *Metrics {
return &Metrics{
metrics: metrics.New(),
}
}

View file

@ -15,19 +15,40 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package web
package metrics
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
metricsPath = "/metrics"
metricsUser = "metrics"
)
func (m *Module) metricsGETHandler(c *gin.Context) {
h := promhttp.Handler()
h.ServeHTTP(c.Writer, c.Request)
type Module struct {
handler http.Handler
}
func New() *Module {
// Use our own gzip handler.
opts := promhttp.HandlerOpts{
DisableCompression: true,
}
// Instrument handler itself.
handler := promhttp.InstrumentMetricHandler(
prometheus.DefaultRegisterer,
promhttp.HandlerFor(prometheus.DefaultGatherer, opts),
)
return &Module{
handler: handler,
}
}
func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
attachHandler(http.MethodGet, "", func(c *gin.Context) {
// Defer all "/metrics" handling to prom.
m.handler.ServeHTTP(c.Writer, c.Request)
})
}

View file

@ -110,19 +110,6 @@ func (m *Module) Route(r *router.Router, mi ...gin.HandlerFunc) {
r.AttachHandler(http.MethodGet, domainBlockListPath, m.domainBlockListGETHandler)
r.AttachHandler(http.MethodGet, tagsPath, m.tagGETHandler)
// Prometheus metrics export endpoint
if config.GetMetricsEnabled() {
metricsGroup := r.AttachGroup(metricsPath)
metricsGroup.Use(mi...)
// Attach basic auth if enabled
if config.GetMetricsAuthEnabled() {
metricsGroup.Use(gin.BasicAuth(gin.Accounts{
config.GetMetricsAuthUsername(): config.GetMetricsAuthPassword(),
}))
}
metricsGroup.Handle(http.MethodGet, "", m.metricsGETHandler)
}
// Attach redirects from old endpoints to current ones for backwards compatibility
r.AttachHandler(http.MethodGet, "/auth/edit", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, userPanelPath) })
r.AttachHandler(http.MethodGet, "/user", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, userPanelPath) })