Monorepo for Tangled tangled.org
5

Configure Feed

Select the types of activity you want to include in your feed.

appview/metrics: setup basic prometheus metrics

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

author
Anirudh Oppiliappan
date (May 11, 2026, 10:50 PM +0300) commit f09f595d parent b601af9c change-id ynlmlxpp
+72
+1
appview/config/config.go
··· 14 14 CookieSecret string `env:"COOKIE_SECRET, default=00000000000000000000000000000000"` 15 15 DbPath string `env:"DB_PATH, default=appview.db"` 16 16 ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:3000"` 17 + MetricsListenAddr string `env:"METRICS_LISTEN_ADDR, default=0.0.0.0:9090"` 17 18 AppviewHost string `env:"APPVIEW_HOST, default=tangled.org"` 18 19 AppviewName string `env:"APPVIEW_NAME, default=Tangled"` 19 20 Dev bool `env:"DEV, default=false"`
+19
appview/metrics/metrics.go
··· 1 + package metrics 2 + 3 + import ( 4 + "github.com/prometheus/client_golang/prometheus" 5 + "github.com/prometheus/client_golang/prometheus/promauto" 6 + ) 7 + 8 + var ( 9 + HttpRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ 10 + Name: "appview_http_requests_total", 11 + Help: "Total number of HTTP requests", 12 + }, []string{"method", "path", "status"}) 13 + 14 + HttpRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ 15 + Name: "appview_http_request_duration_seconds", 16 + Help: "HTTP request duration in seconds", 17 + Buckets: prometheus.DefBuckets, 18 + }, []string{"method", "path", "status"}) 19 + )
+40
appview/metrics/middleware.go
··· 1 + package metrics 2 + 3 + import ( 4 + "fmt" 5 + "net/http" 6 + "time" 7 + 8 + "github.com/go-chi/chi/v5" 9 + ) 10 + 11 + type statusRecorder struct { 12 + http.ResponseWriter 13 + status int 14 + } 15 + 16 + func (r *statusRecorder) WriteHeader(status int) { 17 + r.status = status 18 + r.ResponseWriter.WriteHeader(status) 19 + } 20 + 21 + func Middleware(next http.Handler) http.Handler { 22 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 + rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK} 24 + start := time.Now() 25 + 26 + next.ServeHTTP(rec, r) 27 + 28 + // use the matched route pattern to avoid high cardinality 29 + routePattern := chi.RouteContext(r.Context()).RoutePattern() 30 + if routePattern == "" { 31 + routePattern = "unknown" 32 + } 33 + 34 + status := fmt.Sprintf("%d", rec.status) 35 + duration := time.Since(start).Seconds() 36 + 37 + HttpRequestsTotal.WithLabelValues(r.Method, routePattern, status).Inc() 38 + HttpRequestDuration.WithLabelValues(r.Method, routePattern, status).Observe(duration) 39 + }) 40 + }
+3
appview/state/router.go
··· 11 11 "tangled.org/core/appview/issues" 12 12 "tangled.org/core/appview/knots" 13 13 "tangled.org/core/appview/labels" 14 + "tangled.org/core/appview/metrics" 14 15 "tangled.org/core/appview/middleware" 15 16 "tangled.org/core/appview/migration" 16 17 "tangled.org/core/appview/notifications" ··· 37 38 s.rdb, 38 39 s.logger, 39 40 ) 41 + 42 + router.Use(metrics.Middleware) 40 43 41 44 m := migration.NewMigration(s.db, s.oauth, s.idResolver.Directory(), s.logger) 42 45 router.Use(m.BackgroundMigrationMiddleware)
+9
cmd/appview/main.go
··· 5 5 "net/http" 6 6 "os" 7 7 8 + "github.com/prometheus/client_golang/prometheus/promhttp" 8 9 "tangled.org/core/appview/config" 9 10 "tangled.org/core/appview/state" 10 11 tlog "tangled.org/core/log" ··· 34 35 } 35 36 36 37 logger.Info("starting server", "address", c.Core.ListenAddr) 38 + 39 + go func() { 40 + logger.Info("starting metrics server", "address", c.Core.MetricsListenAddr) 41 + http.Handle("/metrics", promhttp.Handler()) 42 + if err := http.ListenAndServe(c.Core.MetricsListenAddr, nil); err != nil { 43 + logger.Error("failed to start metrics server", "err", err) 44 + } 45 + }() 37 46 38 47 if err := http.ListenAndServe(c.Core.ListenAddr, state.Router()); err != nil { 39 48 logger.Error("failed to start appview", "err", err)