diff options
| author | 2023-11-20 17:43:55 +0200 | |
|---|---|---|
| committer | 2023-11-20 16:43:55 +0100 | |
| commit | 1ba3e14b36c8f00475bdd41cd4a487ef7636836e (patch) | |
| tree | 17405b2de75bb6faaefb9bbfc6487fd7a5efc35a /internal | |
| parent | [bugfix] self-referencing collection pages for status replies (#2364) (diff) | |
| download | gotosocial-1ba3e14b36c8f00475bdd41cd4a487ef7636836e.tar.xz | |
[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>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/config/config.go | 5 | ||||
| -rw-r--r-- | internal/config/defaults.go | 3 | ||||
| -rw-r--r-- | internal/config/helpers.gen.go | 100 | ||||
| -rw-r--r-- | internal/db/bundb/bundb.go | 4 | ||||
| -rw-r--r-- | internal/metrics/metrics.go | 82 | ||||
| -rw-r--r-- | internal/metrics/no_metrics.go | 43 | ||||
| -rw-r--r-- | internal/web/metrics.go | 33 | ||||
| -rw-r--r-- | internal/web/web.go | 13 | 
8 files changed, 283 insertions, 0 deletions
diff --git a/internal/config/config.go b/internal/config/config.go index b7d2eff36..173999b53 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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'"` diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 3da489501..5aba6c689 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -121,6 +121,9 @@ var Defaults = Configuration{  	TracingEndpoint:          "",  	TracingInsecureTransport: false, +	MetricsEnabled:     false, +	MetricsAuthEnabled: false, +  	SyslogEnabled:  false,  	SyslogProtocol: "udp",  	SyslogAddress:  "localhost:514", diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index a008092d3..393a1b1e9 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -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() diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go index a86a20274..2559b8d58 100644 --- a/internal/db/bundb/bundb.go +++ b/internal/db/bundb/bundb.go @@ -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/ diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 000000000..966f346fd --- /dev/null +++ b/internal/metrics/metrics.go @@ -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()), +	) +} diff --git a/internal/metrics/no_metrics.go b/internal/metrics/no_metrics.go new file mode 100644 index 000000000..590a3fbf8 --- /dev/null +++ b/internal/metrics/no_metrics.go @@ -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 +} diff --git a/internal/web/metrics.go b/internal/web/metrics.go new file mode 100644 index 000000000..eb5530290 --- /dev/null +++ b/internal/web/metrics.go @@ -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) +} diff --git a/internal/web/web.go b/internal/web/web.go index 86e74d6f8..6a21a754b 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -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) })  | 
