feat: Add metrics
All checks were successful
/ pre-commit (push) Successful in 1m40s

Fixes #7.

Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
Julien Riou 2025-08-26 09:25:10 +02:00
parent e486d3df98
commit 0ce540e92d
Signed by: jriou
GPG key ID: 9A099EDA51316854
7 changed files with 141 additions and 18 deletions

View file

@ -7,18 +7,22 @@ import (
)
type Config struct {
Title string `json:"title"`
DatabaseType string `json:"database_type"`
DatabaseDsn string `json:"database_dsn"`
IDLength int `json:"id_length"`
PasswordLength int `json:"password_length"`
ExpirationInterval int `json:"expiration_interval"`
ListenAddress string `json:"listen_address"`
ListenPort int `json:"listen_port"`
Expirations []int `json:"expirations"`
Expiration int `json:"expiration"`
MaxUploadSize int64 `json:"max_upload_size"`
ShowVersion bool `json:"show_version"`
Title string `json:"title"`
DatabaseType string `json:"database_type"`
DatabaseDsn string `json:"database_dsn"`
IDLength int `json:"id_length"`
PasswordLength int `json:"password_length"`
ExpirationInterval int `json:"expiration_interval"`
ListenAddress string `json:"listen_address"`
ListenPort int `json:"listen_port"`
Expirations []int `json:"expirations"`
Expiration int `json:"expiration"`
MaxUploadSize int64 `json:"max_upload_size"`
ShowVersion bool `json:"show_version"`
EnableMetrics bool `json:"enable_metrics"`
PrometheusRoute string `json:"prometheus_route"`
PrometheusNotesMetric string `json:"prometheus_notes_metric"`
ObservationInterval int `json:"observation_internal"`
}
func NewConfig() *Config {
@ -38,9 +42,13 @@ func NewConfig() *Config {
604800, // 7 days
18144000, // 30 days
},
Expiration: 604800, // 7 days
MaxUploadSize: 10485760, // 10MiB (encoded)
ShowVersion: false,
Expiration: 604800, // 7 days
MaxUploadSize: 10485760, // 10MiB (encoded)
ShowVersion: false,
EnableMetrics: false,
PrometheusRoute: "/metrics",
PrometheusNotesMetric: "collerd_notes",
ObservationInterval: 60,
}
}

55
src/server/metrics.go Normal file
View file

@ -0,0 +1,55 @@
package server
import (
"fmt"
"log/slog"
"time"
"github.com/prometheus/client_golang/prometheus"
)
type Metrics struct {
logger *slog.Logger
notes prometheus.Gauge
db *Database
reg *prometheus.Registry
interval int
}
func NewMetrics(logger *slog.Logger, reg *prometheus.Registry, config *Config, db *Database) (*Metrics, error) {
l := logger.With("module", "metrics")
m := &Metrics{
logger: l,
db: db,
notes: prometheus.NewGauge(prometheus.GaugeOpts{
Name: config.PrometheusNotesMetric,
}),
reg: reg,
interval: config.ObservationInterval,
}
if err := reg.Register(m.notes); err != nil {
return nil, err
}
go m.ObserveThread()
return m, nil
}
func (m *Metrics) ObserveThread() {
m.logger.Debug("starting observations")
for {
m.logger.Debug("counting notes")
var count int64
m.db.db.Model(&Note{}).Count(&count)
m.notes.Set(float64(count))
m.logger.Debug("counted notes", slog.Any("count", count))
wording := "second"
if m.interval > 1 {
wording += "s"
}
m.logger.Debug(fmt.Sprintf("waiting for %d %s before next observation", m.interval, wording))
time.Sleep(time.Duration(m.interval) * time.Second)
}
}

View file

@ -14,6 +14,7 @@ import (
"git.riou.xyz/jriou/coller/internal"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var passwordLength = internal.MIN_PASSWORD_LENGTH
@ -23,6 +24,7 @@ type Server struct {
db *Database
config *Config
version string
metrics *Metrics
}
func NewServer(logger *slog.Logger, db *Database, config *Config, version string) (*Server, error) {
@ -44,6 +46,10 @@ func (s *Server) SetPasswordLength(length int) {
passwordLength = length
}
func (s *Server) SetMetrics(metrics *Metrics) {
s.metrics = metrics
}
type ErrorResponse struct {
Message string `json:"message"`
Error string `json:"error"`
@ -312,8 +318,15 @@ var templatesFS embed.FS
func (s *Server) Start() error {
r := mux.NewRouter().StrictSlash(true)
// API
// Healthchecks
r.HandleFunc("/health", HealthHandler)
// Metrics
if s.metrics != nil && s.metrics.reg != nil {
r.Path(s.config.PrometheusRoute).Handler(promhttp.HandlerFor(s.metrics.reg, promhttp.HandlerOpts{Registry: s.metrics.reg})).Methods("GET")
}
// API
r.Path("/api/note").Handler(&CreateNoteHandler{logger: s.logger, db: s.db, maxUploadSize: s.config.MaxUploadSize}).Methods("POST")
r.Path("/{id:[a-zA-Z0-9]+}/{password:[a-zA-Z0-9]+}").Handler(&GetProtectedNoteHandler{logger: s.logger, db: s.db}).Methods("GET")
r.Path("/{id:[a-zA-Z0-9]+}").Handler(&GetNoteHandler{logger: s.logger, db: s.db}).Methods("GET")