Fixes #7. Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
parent
e486d3df98
commit
0ce540e92d
7 changed files with 141 additions and 18 deletions
|
@ -27,5 +27,9 @@ The file format is **JSON**:
|
|||
* **expiration** (int): Default expiration time in seconds
|
||||
* **max_upload_size** (int): Maximum number of bytes received by the server for notes (the base64 encoding may use more space than the real size of the note)
|
||||
* **show_version** (bool): Show version on the website
|
||||
* **enable_metrics** (bool): Enable Prometheus endpoint (default false)
|
||||
* **prometheus_route** (string): Route to expose Prometheus metrics (default "/metrics")
|
||||
* **prometheus_notes_metric** (string): Name of the notes count metric (default "collerd_notes")
|
||||
* **observation_internal** (int): Number of seconds to wait between two observations (default 60)
|
||||
|
||||
The configuration file is not required but the service might not be exposed to the public.
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"git.riou.xyz/jriou/coller/internal"
|
||||
"git.riou.xyz/jriou/coller/server"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -75,6 +76,16 @@ func handleMain() int {
|
|||
srv.SetIDLength(config.IDLength)
|
||||
srv.SetPasswordLength(config.PasswordLength)
|
||||
|
||||
if config.EnableMetrics {
|
||||
reg := prometheus.NewRegistry()
|
||||
metrics, err := server.NewMetrics(logger, reg, config, db)
|
||||
if err != nil {
|
||||
logger.Error("could not register metrics", slog.Any("error", err))
|
||||
return internal.RC_ERROR
|
||||
}
|
||||
srv.SetMetrics(metrics)
|
||||
}
|
||||
|
||||
err = srv.Start()
|
||||
if err != nil {
|
||||
logger.Error("could not start server", slog.Any("error", err))
|
||||
|
|
|
@ -6,6 +6,7 @@ toolchain go1.24.6
|
|||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
golang.design/x/clipboard v0.7.1
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/term v0.34.0
|
||||
|
@ -15,6 +16,8 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
|
@ -22,10 +25,15 @@ require (
|
|||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
|
||||
golang.org/x/image v0.28.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
)
|
||||
|
|
28
src/go.sum
28
src/go.sum
|
@ -1,6 +1,12 @@
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
|
@ -15,15 +21,31 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
|
||||
golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
|
@ -42,6 +64,8 @@ golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
|||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
@ -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
55
src/server/metrics.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue