From 8f09aa959b148a3fc1d882ff419cb206250deb75 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Fri, 2 Feb 2024 21:07:29 +0100 Subject: [PATCH] feat: Implement simple page metrics --- cmd/rio/main.go | 32 +++++++++++++++++----- internal/pages/metrics.go | 56 +++++++++++++++++++++++++++++++++++++++ internal/pages/pages.go | 7 ++++- 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 internal/pages/metrics.go diff --git a/cmd/rio/main.go b/cmd/rio/main.go index 8d0caf6..349d490 100644 --- a/cmd/rio/main.go +++ b/cmd/rio/main.go @@ -24,7 +24,7 @@ import ( "github.com/urfave/cli/v2" ) -func handleSubdomain(pagesDomain, domain, cname, path, giteaUrl, defaultCsp string, giteaClient *repo.GiteaClient, w http.ResponseWriter) { +func handleSubdomain(pagesDomain, domain, cname, path, giteaUrl, defaultCsp string, giteaClient *repo.GiteaClient, lokiConfig *pages.LokiMetricConfig, w http.ResponseWriter) { username := "" if cname != "" { // If we are accessed via a CNAME, then CNAME contains our . value. @@ -60,10 +60,10 @@ func handleSubdomain(pagesDomain, domain, cname, path, giteaUrl, defaultCsp stri return } - pages.ServeFile(username, repo.Name, path, defaultCsp, giteaClient, w) + pages.ServeFile(username, repo.Name, path, defaultCsp, domain, giteaClient, lokiConfig, w) } -func Handler(pagesDomain, giteaUrl, defaultCsp string, giteaClient *repo.GiteaClient) http.HandlerFunc { +func Handler(pagesDomain, giteaUrl, defaultCsp string, giteaClient *repo.GiteaClient, lokiConfig *pages.LokiMetricConfig) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Server", "rio") @@ -79,7 +79,7 @@ func Handler(pagesDomain, giteaUrl, defaultCsp string, giteaClient *repo.GiteaCl // Is a direct subdomain requested? if strings.HasSuffix(req.Host, pagesDomain) { log.Debug("Domain can be directly handled") - handleSubdomain(pagesDomain, req.Host, "", req.URL.Path, giteaUrl, defaultCsp, giteaClient, w) + handleSubdomain(pagesDomain, req.Host, "", req.URL.Path, giteaUrl, defaultCsp, giteaClient, lokiConfig, w) return } @@ -96,7 +96,7 @@ func Handler(pagesDomain, giteaUrl, defaultCsp string, giteaClient *repo.GiteaCl // pages domain makes no sense. if strings.HasSuffix(cname, "."+pagesDomain) { log.Debugf("%s is alias of %s and can be handled after a CNAME query", req.Host, cname) - handleSubdomain(pagesDomain, req.Host, cname, req.URL.Path, giteaUrl, defaultCsp, giteaClient, w) + handleSubdomain(pagesDomain, req.Host, cname, req.URL.Path, giteaUrl, defaultCsp, giteaClient, lokiConfig, w) return } @@ -131,6 +131,7 @@ func runServer(ctx *cli.Context) error { acmeDnsProvider := ctx.String("acme-dns-provider") acmeDisable := ctx.Bool("acme-disable") defaultCsp := ctx.String("default-csp") + lokiUrl := ctx.String("loki-url") // Init Logging if ctx.Bool("debug") { @@ -139,6 +140,19 @@ func runServer(ctx *cli.Context) error { log.SetLevel(log.InfoLevel) } + // Set up the Loki metrics + var lokiConfig pages.LokiMetricConfig + if lokiUrl == "" { + lokiConfig = pages.LokiMetricConfig{ + Enabled: false, + } + } else { + lokiConfig = pages.LokiMetricConfig{ + Enabled: true, + Url: lokiUrl, + } + } + // Setup the Gitea stuff httpClient := http.Client{Timeout: 10 * time.Second} giteaApiClient, err := gitea.NewClient( @@ -240,7 +254,7 @@ func runServer(ctx *cli.Context) error { defer waitGroup.Done() log.Debug("Listening on main HTTP server") - if err := http.Serve(listener, Handler(domain, giteaUrl, defaultCsp, &giteaClient)); err != nil { + if err := http.Serve(listener, Handler(domain, giteaUrl, defaultCsp, &giteaClient, &lokiConfig)); err != nil { log.Fatal(fmt.Errorf("Listening failed: %v", err)) } log.Debug("Listening on main HTTP server done!") @@ -350,6 +364,12 @@ func main() { Value: "", EnvVars: []string{"DEFAULT_CSP"}, }, + &cli.StringFlag{ + Name: "loki-url", + Usage: "The URL for Loki metric pings", + Value: "", + EnvVars: []string{"LOKI_URL"}, + }, }, } diff --git a/internal/pages/metrics.go b/internal/pages/metrics.go new file mode 100644 index 0000000..1b882e6 --- /dev/null +++ b/internal/pages/metrics.go @@ -0,0 +1,56 @@ +package pages + +import ( + "encoding/json" + "net/http" + "strconv" + "strings" + "time" + + log "github.com/sirupsen/logrus" +) + +type LokiMetricConfig struct { + Url string + Enabled bool +} + +// Checks if we should send a metric ping to Loki based on the served path. +func (c *LokiMetricConfig) shouldSendMetrics(path string) bool { + return strings.HasSuffix(path, ".html") && c.Enabled +} + +func (c *LokiMetricConfig) sendMetricPing(domain, path string) { + data := map[string]interface{}{ + "steams": []map[string]interface{}{ + { + "stream": map[string]string{ + // Labels + "service": "rio", + "domain": domain, + "type": "metric", + }, + "values": [][]interface{}{ + { + strconv.Itoa(int(time.Now().UnixNano())), + "path=" + path, + }, + }, + }, + }, + } + jsonData, err := json.Marshal(data) + if err != nil { + log.Errorf("Failed to send metric ping to Loki: %v", err) + return + } + + // Send the ping to the Loki server + go func() { + http.Post( + c.Url, + "application/json", + strings.NewReader(string(jsonData)), + ) + }() +} diff --git a/internal/pages/pages.go b/internal/pages/pages.go index 464d361..8e14c57 100644 --- a/internal/pages/pages.go +++ b/internal/pages/pages.go @@ -45,7 +45,7 @@ func addHeaders(csp, contentType string, contentLength int, w http.ResponseWrite } } -func ServeFile(username, reponame, path, defaultCsp string, giteaClient *repo.GiteaClient, w http.ResponseWriter) { +func ServeFile(username, reponame, path, defaultCsp, domain string, giteaClient *repo.GiteaClient, metricConfig *LokiMetricConfig, w http.ResponseWriter) { // Strip away a starting / as it messes with Gitea if path[:1] == "/" { path = path[1:] @@ -116,4 +116,9 @@ func ServeFile(username, reponame, path, defaultCsp string, giteaClient *repo.Gi addHeaders(csp, mimeType, len(content), w) w.WriteHeader(200) w.Write(content) + + // Tell Loki about if, if desired + if metricConfig.shouldSendMetrics(path) { + metricConfig.sendMetricPing(domain, path) + } }