diff --git a/cmd/tclipd/main.go b/cmd/tclipd/main.go index e031689..eea8130 100644 --- a/cmd/tclipd/main.go +++ b/cmd/tclipd/main.go @@ -26,6 +26,9 @@ import ( _ "github.com/lib/pq" "github.com/microcosm-cc/bluemonday" "github.com/niklasfasching/go-org/org" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/russross/blackfriday" _ "modernc.org/sqlite" "tailscale.com/client/tailscale" @@ -43,6 +46,7 @@ var ( hidePasteUserInfo = flag.Bool("hide-funnel-users", hasEnv("HIDE_FUNNEL_USERS"), "if set, display the username and profile picture of the user who created the paste in funneled pastes") databaseUrl = flag.String("database-url", envOr("DATABASE_URL", ""), "optional database url if you'd rather use Postgres instead of sqlite") httpPort = flag.String("http-port", envOr("HTTP_PORT", ""), "optional http port to start an http server on, e.g for reverse proxies. will only serve funnel endpoints") + enableMetrics = flag.Bool("enable-metrics", hasEnv("ENABLE_METRICS"), "if set, enabled the /metrics endpoint on the tailnet listener") //go:embed schema.sql sqlSchema string @@ -63,6 +67,17 @@ func hasEnv(name string) bool { const formDataLimit = 64 * 1024 // 64 kilobytes (approx. 32 printed pages of text) +var ( + totalPastes = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "tclip_total_pastes", + Help: "The total number of stored pastes", + }) + pasteViews = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "tclip_paste_views", + Help: "Number of times a paste has been viewed", + }, []string{"id", "origin", "type"}) +) + func dataLocation() string { if dir, ok := os.LookupEnv("DATA_DIR"); ok { return dir @@ -264,7 +279,7 @@ VALUES } log.Printf("new paste: %s", id) - + totalPastes.Inc() switch r.Header.Get("Accept") { case "text/plain": w.WriteHeader(http.StatusOK) @@ -460,7 +475,7 @@ WHERE id = $1 AND user_id = $2 s.ShowError(w, r, err, http.StatusInternalServerError) return } - + totalPastes.Dec() http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } @@ -470,11 +485,12 @@ func (s *Server) ShowPost(w http.ResponseWriter, r *http.Request) { if ui != nil { up = ui.UserProfile } - + origin := "tailnet" if valAny := r.Context().Value(privacyKey); valAny != nil { if val, ok := valAny.(mixedCriticalityHandlerCtxKey); ok { if val == isFunnel { up = nil + origin = "public" } } } @@ -572,6 +588,9 @@ WHERE p.id = $1` w.WriteHeader(http.StatusOK) fmt.Fprint(w, data) + + pasteViews.With(prometheus.Labels{"id": id, "origin": origin, "type": "raw"}).Inc() + return // download file to disk (plain text view plus download hint) case "dl": @@ -581,6 +600,8 @@ WHERE p.id = $1` w.WriteHeader(http.StatusOK) fmt.Fprint(w, data) + pasteViews.With(prometheus.Labels{"id": id, "origin": origin, "type": "download"}).Inc() + return case "": // view markdown file with a fancy HTML rendering step @@ -603,6 +624,7 @@ WHERE p.id = $1` title = ogTitle } } + pasteViews.With(prometheus.Labels{"id": id, "origin": origin, "type": "fancy"}).Inc() err = s.tmpls.ExecuteTemplate(w, "fancypost.html", struct { Title string @@ -635,6 +657,8 @@ WHERE p.id = $1` remoteUserID = up.ID } + pasteViews.With(prometheus.Labels{"id": id, "origin": origin, "type": "normal"}).Inc() + err = s.tmpls.ExecuteTemplate(w, "showpaste.html", struct { UserInfo *tailcfg.UserProfile Title string @@ -693,6 +717,17 @@ func main() { } defer db.Close() + if *enableMetrics { + q := ` +SELECT count(*) +FROM pastes +` + var pastes float64 + // Execute query using 'id' and place value into 'output' + db.QueryRow(q).Scan(&pastes) + totalPastes.Set(pastes) + } + lc, err := s.LocalClient() if err != nil { log.Fatal(err) @@ -736,6 +771,11 @@ func main() { tailnetMux.HandleFunc("/", srv.TailnetIndex) tailnetMux.HandleFunc("/help", srv.TailnetHelp) + if *enableMetrics { + tailnetMux.Handle("/metrics", promhttp.Handler()) + log.Printf("metrics enabled on /metrics") + } + funnelMux := http.NewServeMux() funnelMux.Handle("/static/", http.FileServer(http.FS(staticFiles))) funnelMux.HandleFunc("/", srv.PublicIndex) diff --git a/flake.nix b/flake.nix index 9f5bd07..cdfd81a 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,7 @@ go = pkgs.go; src = ./.; subPackages = "cmd/tclipd"; - vendorHash = "sha256-Ho39aNWTbygA46/9lkXRVLV6FLkWenAJKxOE3MJUb2M="; + vendorHash = "sha256-nxKlKxpr7PZhDLk/J3TBjdhLjy7EYqmMGF+y4hgRgRQ="; }; tclip = pkgs.buildGo122Module { diff --git a/go.mod b/go.mod index 6d8a5ce..9c062b1 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect github.com/aws/smithy-go v1.19.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/coreos/go-iptables v0.7.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect @@ -68,6 +70,10 @@ require ( github.com/mitchellh/go-ps v1.0.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/safchain/ethtool v0.3.0 // indirect github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect @@ -98,6 +104,7 @@ require ( golang.org/x/tools v0.17.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.41.0 // indirect diff --git a/go.sum b/go.sum index 8f81f38..3d65b11 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,10 @@ github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= @@ -158,6 +162,14 @@ github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= 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.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -251,6 +263,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=