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

View file

@ -212,6 +212,7 @@
var ( var (
authModule = api.NewAuth(state.DB, processor, idp, routerSession, sessionName) // auth/oauth paths authModule = api.NewAuth(state.DB, processor, idp, routerSession, sessionName) // auth/oauth paths
clientModule = api.NewClient(state.DB, processor) // api client endpoints clientModule = api.NewClient(state.DB, processor) // api client endpoints
metricsModule = api.NewMetrics() // Metrics endpoints
fileserverModule = api.NewFileserver(processor) // fileserver endpoints fileserverModule = api.NewFileserver(processor) // fileserver endpoints
wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints wellKnownModule = api.NewWellKnown(processor) // .well-known endpoints
nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint nodeInfoModule = api.NewNodeInfo(processor) // nodeinfo endpoint
@ -222,6 +223,7 @@
// these should be routed in order // these should be routed in order
authModule.Route(router) authModule.Route(router)
clientModule.Route(router) clientModule.Route(router)
metricsModule.Route(router)
fileserverModule.Route(router) fileserverModule.Route(router)
wellKnownModule.Route(router) wellKnownModule.Route(router)
nodeInfoModule.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 // 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/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package web package metrics
import ( import (
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
) )
const ( type Module struct {
metricsPath = "/metrics" handler http.Handler
metricsUser = "metrics" }
)
func New() *Module {
func (m *Module) metricsGETHandler(c *gin.Context) { // Use our own gzip handler.
h := promhttp.Handler() opts := promhttp.HandlerOpts{
h.ServeHTTP(c.Writer, c.Request) 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, domainBlockListPath, m.domainBlockListGETHandler)
r.AttachHandler(http.MethodGet, tagsPath, m.tagGETHandler) 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 // 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, "/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) }) r.AttachHandler(http.MethodGet, "/user", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, userPanelPath) })