various: HTTP server; migrate to cobra pattern for repository
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ifac6e992b77dfaf92e3059944aa871f16a6a6964
This commit is contained in:
parent
e0ec475a81
commit
b894833ac7
6 changed files with 182 additions and 0 deletions
15
cmd/watchdog/main.go
Normal file
15
cmd/watchdog/main.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package watchdog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Main() {
|
||||||
|
configPath := flag.String("config", "config.yaml", "path to config file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := Run(*configPath); err != nil {
|
||||||
|
log.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
68
cmd/watchdog/root.go
Normal file
68
cmd/watchdog/root.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
package watchdog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"notashelf.dev/watchdog/internal/aggregate"
|
||||||
|
"notashelf.dev/watchdog/internal/api"
|
||||||
|
"notashelf.dev/watchdog/internal/config"
|
||||||
|
"notashelf.dev/watchdog/internal/normalize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(configPath string) error {
|
||||||
|
// Load configuration
|
||||||
|
cfg, err := config.Load(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Loaded config for domain: %s", cfg.Site.Domain)
|
||||||
|
|
||||||
|
// Initialize components
|
||||||
|
pathNormalizer := normalize.NewPathNormalizer(cfg.Site.Path)
|
||||||
|
pathRegistry := aggregate.NewPathRegistry(cfg.Limits.MaxPaths)
|
||||||
|
refRegistry := normalize.NewReferrerRegistry(cfg.Limits.MaxSources)
|
||||||
|
metricsAgg := aggregate.NewMetricsAggregator(pathRegistry, *cfg)
|
||||||
|
|
||||||
|
// Register Prometheus metrics
|
||||||
|
promRegistry := prometheus.NewRegistry()
|
||||||
|
metricsAgg.MustRegister(promRegistry)
|
||||||
|
|
||||||
|
// Create HTTP handlers
|
||||||
|
ingestionHandler := api.NewIngestionHandler(*cfg, pathNormalizer, pathRegistry, refRegistry, metricsAgg)
|
||||||
|
|
||||||
|
// Setup routes
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
// Metrics endpoint
|
||||||
|
mux.Handle(cfg.Server.MetricsPath, promhttp.HandlerFor(promRegistry, promhttp.HandlerOpts{
|
||||||
|
EnableOpenMetrics: true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ingestion endpoint
|
||||||
|
mux.Handle(cfg.Server.IngestionPath, ingestionHandler)
|
||||||
|
|
||||||
|
// Health check endpoint
|
||||||
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("OK"))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serve static files from /web/ if the directory exists
|
||||||
|
if info, err := os.Stat("web"); err == nil && info.IsDir() {
|
||||||
|
log.Println("Serving static files from /web/")
|
||||||
|
mux.Handle("/web/", http.StripPrefix("/web/", http.FileServer(http.Dir("web"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
log.Printf("Starting server on %s", cfg.Server.ListenAddr)
|
||||||
|
log.Printf("Metrics endpoint: %s", cfg.Server.MetricsPath)
|
||||||
|
log.Printf("Ingestion endpoint: %s", cfg.Server.IngestionPath)
|
||||||
|
|
||||||
|
return http.ListenAndServe(cfg.Server.ListenAddr, mux)
|
||||||
|
}
|
||||||
56
config.example.yaml
Normal file
56
config.example.yaml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Watchdog Analytics Configuration Example
|
||||||
|
|
||||||
|
site:
|
||||||
|
# Your site's primary domain (required)
|
||||||
|
domain: "example.com"
|
||||||
|
|
||||||
|
# Salt rotation for IP hashing (optional, default: daily)
|
||||||
|
# Options: "daily", "hourly"
|
||||||
|
salt_rotation: "daily"
|
||||||
|
|
||||||
|
# Which dimensions to collect
|
||||||
|
collect:
|
||||||
|
pageviews: true
|
||||||
|
country: true
|
||||||
|
device: true
|
||||||
|
# Referrer collection mode: "off", "domain"
|
||||||
|
# "domain" - collect only the domain part (e.g., "google.com")
|
||||||
|
# "off" - don't collect referrer data
|
||||||
|
referrer: "domain"
|
||||||
|
|
||||||
|
# Custom events to track (optional)
|
||||||
|
custom_events:
|
||||||
|
- "signup"
|
||||||
|
- "purchase"
|
||||||
|
- "download"
|
||||||
|
|
||||||
|
# Path normalization options
|
||||||
|
path:
|
||||||
|
# Remove query strings from paths (e.g., /page?id=1 -> /page)
|
||||||
|
strip_query: true
|
||||||
|
# Remove fragments from paths (e.g., /page#section -> /page)
|
||||||
|
strip_fragment: true
|
||||||
|
# Collapse numeric segments to :id (e.g., /user/123 -> /user/:id)
|
||||||
|
collapse_numeric_segments: true
|
||||||
|
# Maximum number of path segments to keep
|
||||||
|
max_segments: 5
|
||||||
|
# Normalize trailing slashes (e.g., /page/ -> /page)
|
||||||
|
normalize_trailing_slash: true
|
||||||
|
|
||||||
|
# Cardinality limits to prevent metric explosion
|
||||||
|
limits:
|
||||||
|
# Maximum number of unique paths to track
|
||||||
|
max_paths: 10000
|
||||||
|
# Maximum number of unique referrer sources to track
|
||||||
|
max_sources: 500
|
||||||
|
# Maximum events per minute (for rate limiting, not yet implemented)
|
||||||
|
max_events_per_minute: 1000
|
||||||
|
|
||||||
|
# Server configuration
|
||||||
|
server:
|
||||||
|
# Address to listen on
|
||||||
|
listen_addr: ":8080"
|
||||||
|
# Prometheus metrics endpoint
|
||||||
|
metrics_path: "/metrics"
|
||||||
|
# Event ingestion endpoint
|
||||||
|
ingestion_path: "/api/event"
|
||||||
14
go.mod
14
go.mod
|
|
@ -5,3 +5,17 @@ go 1.25.5
|
||||||
require gopkg.in/yaml.v3 v3.0.1
|
require gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
||||||
require golang.org/x/net v0.51.0
|
require golang.org/x/net v0.51.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
|
)
|
||||||
|
|
|
||||||
22
go.sum
22
go.sum
|
|
@ -1,5 +1,27 @@
|
||||||
|
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
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/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
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.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
|
||||||
7
main.go
Normal file
7
main.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "notashelf.dev/watchdog/cmd/watchdog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
watchdog.Main()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue