mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-26 15:23:33 -06:00
[feature] Initial Prometheus metrics implementation (#2334)
* feat: Initial OTEL metrics * docs: add metrics documentation * fix: metrics endpoint conditional check * feat: metrics endpoint basic auth * fix: make metrics-auth-enabled default false * fix: go fmt helpers.gen.go * fix: add metric-related env vars to envparsing.sh * fix: metrics docs * fix: metrics related stuff in envparsing.sh * fix: metrics docs * chore: metrics docs wording * fix: metrics stuff in envparsing? * bump otel versions --------- Co-authored-by: Tsuribori <user@acertaindebian> Co-authored-by: Tsuribori <none@example.org> Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
This commit is contained in:
parent
16275853eb
commit
1ba3e14b36
251 changed files with 48389 additions and 22 deletions
|
|
@ -140,6 +140,11 @@ type Configuration struct {
|
|||
TracingEndpoint string `name:"tracing-endpoint" usage:"Endpoint of your trace collector. Eg., 'localhost:4317' for gRPC, 'localhost:4318' for http"`
|
||||
TracingInsecureTransport bool `name:"tracing-insecure-transport" usage:"Disable TLS for the gRPC or HTTP transport protocol"`
|
||||
|
||||
MetricsEnabled bool `name:"metrics-enabled" usage:"Enable OpenTelemetry based metrics support."`
|
||||
MetricsAuthEnabled bool `name:"metrics-auth-enabled" usage:"Enable HTTP Basic Authentication for Prometheus metrics endpoint"`
|
||||
MetricsAuthUsername string `name:"metrics-auth-username" usage:"Username for Prometheus metrics endpoint"`
|
||||
MetricsAuthPassword string `name:"metrics-auth-password" usage:"Password for Prometheus metrics endpoint"`
|
||||
|
||||
SMTPHost string `name:"smtp-host" usage:"Host of the smtp server. Eg., 'smtp.eu.mailgun.org'"`
|
||||
SMTPPort int `name:"smtp-port" usage:"Port of the smtp server. Eg., 587"`
|
||||
SMTPUsername string `name:"smtp-username" usage:"Username to authenticate with the smtp server as. Eg., 'postmaster@mail.example.org'"`
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ var Defaults = Configuration{
|
|||
TracingEndpoint: "",
|
||||
TracingInsecureTransport: false,
|
||||
|
||||
MetricsEnabled: false,
|
||||
MetricsAuthEnabled: false,
|
||||
|
||||
SyslogEnabled: false,
|
||||
SyslogProtocol: "udp",
|
||||
SyslogAddress: "localhost:514",
|
||||
|
|
|
|||
|
|
@ -2100,6 +2100,106 @@ func GetTracingInsecureTransport() bool { return global.GetTracingInsecureTransp
|
|||
// SetTracingInsecureTransport safely sets the value for global configuration 'TracingInsecureTransport' field
|
||||
func SetTracingInsecureTransport(v bool) { global.SetTracingInsecureTransport(v) }
|
||||
|
||||
// GetMetricsEnabled safely fetches the Configuration value for state's 'MetricsEnabled' field
|
||||
func (st *ConfigState) GetMetricsEnabled() (v bool) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.MetricsEnabled
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetMetricsEnabled safely sets the Configuration value for state's 'MetricsEnabled' field
|
||||
func (st *ConfigState) SetMetricsEnabled(v bool) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.MetricsEnabled = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// MetricsEnabledFlag returns the flag name for the 'MetricsEnabled' field
|
||||
func MetricsEnabledFlag() string { return "metrics-enabled" }
|
||||
|
||||
// GetMetricsEnabled safely fetches the value for global configuration 'MetricsEnabled' field
|
||||
func GetMetricsEnabled() bool { return global.GetMetricsEnabled() }
|
||||
|
||||
// SetMetricsEnabled safely sets the value for global configuration 'MetricsEnabled' field
|
||||
func SetMetricsEnabled(v bool) { global.SetMetricsEnabled(v) }
|
||||
|
||||
// GetMetricsAuthEnabled safely fetches the Configuration value for state's 'MetricsAuthEnabled' field
|
||||
func (st *ConfigState) GetMetricsAuthEnabled() (v bool) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.MetricsAuthEnabled
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetMetricsAuthEnabled safely sets the Configuration value for state's 'MetricsAuthEnabled' field
|
||||
func (st *ConfigState) SetMetricsAuthEnabled(v bool) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.MetricsAuthEnabled = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// MetricsAuthEnabledFlag returns the flag name for the 'MetricsAuthEnabled' field
|
||||
func MetricsAuthEnabledFlag() string { return "metrics-auth-enabled" }
|
||||
|
||||
// GetMetricsAuthEnabled safely fetches the value for global configuration 'MetricsAuthEnabled' field
|
||||
func GetMetricsAuthEnabled() bool { return global.GetMetricsAuthEnabled() }
|
||||
|
||||
// SetMetricsAuthEnabled safely sets the value for global configuration 'MetricsAuthEnabled' field
|
||||
func SetMetricsAuthEnabled(v bool) { global.SetMetricsAuthEnabled(v) }
|
||||
|
||||
// GetMetricsAuthUsername safely fetches the Configuration value for state's 'MetricsAuthUsername' field
|
||||
func (st *ConfigState) GetMetricsAuthUsername() (v string) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.MetricsAuthUsername
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetMetricsAuthUsername safely sets the Configuration value for state's 'MetricsAuthUsername' field
|
||||
func (st *ConfigState) SetMetricsAuthUsername(v string) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.MetricsAuthUsername = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// MetricsAuthUsernameFlag returns the flag name for the 'MetricsAuthUsername' field
|
||||
func MetricsAuthUsernameFlag() string { return "metrics-auth-username" }
|
||||
|
||||
// GetMetricsAuthUsername safely fetches the value for global configuration 'MetricsAuthUsername' field
|
||||
func GetMetricsAuthUsername() string { return global.GetMetricsAuthUsername() }
|
||||
|
||||
// SetMetricsAuthUsername safely sets the value for global configuration 'MetricsAuthUsername' field
|
||||
func SetMetricsAuthUsername(v string) { global.SetMetricsAuthUsername(v) }
|
||||
|
||||
// GetMetricsAuthPassword safely fetches the Configuration value for state's 'MetricsAuthPassword' field
|
||||
func (st *ConfigState) GetMetricsAuthPassword() (v string) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.MetricsAuthPassword
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetMetricsAuthPassword safely sets the Configuration value for state's 'MetricsAuthPassword' field
|
||||
func (st *ConfigState) SetMetricsAuthPassword(v string) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.MetricsAuthPassword = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// MetricsAuthPasswordFlag returns the flag name for the 'MetricsAuthPassword' field
|
||||
func MetricsAuthPasswordFlag() string { return "metrics-auth-password" }
|
||||
|
||||
// GetMetricsAuthPassword safely fetches the value for global configuration 'MetricsAuthPassword' field
|
||||
func GetMetricsAuthPassword() string { return global.GetMetricsAuthPassword() }
|
||||
|
||||
// SetMetricsAuthPassword safely sets the value for global configuration 'MetricsAuthPassword' field
|
||||
func SetMetricsAuthPassword(v string) { global.SetMetricsAuthPassword(v) }
|
||||
|
||||
// GetSMTPHost safely fetches the Configuration value for state's 'SMTPHost' field
|
||||
func (st *ConfigState) GetSMTPHost() (v string) {
|
||||
st.mutex.RLock()
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/metrics"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/tracing"
|
||||
"github.com/uptrace/bun"
|
||||
|
|
@ -142,6 +143,9 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
|
|||
if config.GetTracingEnabled() {
|
||||
db.AddQueryHook(tracing.InstrumentBun())
|
||||
}
|
||||
if config.GetMetricsEnabled() {
|
||||
db.AddQueryHook(metrics.InstrumentBun())
|
||||
}
|
||||
|
||||
// table registration is needed for many-to-many, see:
|
||||
// https://bun.uptrace.dev/orm/many-to-many-relation/
|
||||
|
|
|
|||
82
internal/metrics/metrics.go
Normal file
82
internal/metrics/metrics.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// 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/>.
|
||||
|
||||
//go:build !nometrics
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/technologize/otel-go-contrib/otelginmetrics"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/extra/bunotel"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||
sdk "go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||
)
|
||||
|
||||
const (
|
||||
serviceName = "GoToSocial"
|
||||
)
|
||||
|
||||
func Initialize() error {
|
||||
if !config.GetMetricsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.GetMetricsAuthEnabled() {
|
||||
if config.GetMetricsAuthPassword() == "" || config.GetMetricsAuthUsername() == "" {
|
||||
return errors.New("metrics-auth-username and metrics-auth-password must be set when metrics-auth-enabled is true")
|
||||
}
|
||||
}
|
||||
|
||||
r, _ := resource.Merge(
|
||||
resource.Default(),
|
||||
resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceName(serviceName),
|
||||
),
|
||||
)
|
||||
|
||||
prometheusExporter, err := prometheus.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meterProvider := sdk.NewMeterProvider(
|
||||
sdk.WithResource(r),
|
||||
sdk.WithReader(prometheusExporter),
|
||||
)
|
||||
otel.SetMeterProvider(meterProvider)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InstrumentGin() gin.HandlerFunc {
|
||||
return otelginmetrics.Middleware(serviceName)
|
||||
}
|
||||
|
||||
func InstrumentBun() bun.QueryHook {
|
||||
return bunotel.NewQueryHook(
|
||||
bunotel.WithMeterProvider(otel.GetMeterProvider()),
|
||||
)
|
||||
}
|
||||
43
internal/metrics/no_metrics.go
Normal file
43
internal/metrics/no_metrics.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// 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/>.
|
||||
|
||||
//go:build nometrics
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func Initialize() error {
|
||||
if config.GetMetricsEnabled() {
|
||||
return errors.New("metrics was disabled at build time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InstrumentGin() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {}
|
||||
}
|
||||
|
||||
func InstrumentBun() bun.QueryHook {
|
||||
return nil
|
||||
}
|
||||
33
internal/web/metrics.go
Normal file
33
internal/web/metrics.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// 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 web
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"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)
|
||||
}
|
||||
|
|
@ -110,6 +110,19 @@ 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) })
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue