summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-14 12:29:01 -0800
committerbndw <ben@bdw.to>2026-02-14 12:29:01 -0800
commitc33395cee66770c4d88a47537c0ed821f6585f62 (patch)
treee7f61e5881089f2f10bd06d5828cd7c58b2bc98e
parenteb666af39feed4be9c8c1354cf52d0ea38ab5d36 (diff)
fix: properly chain gRPC interceptors and fix dashboard uptime
Two critical fixes for metrics: 1. Fix interceptor chaining - Changed from mixed grpc.UnaryInterceptor/ChainUnaryInterceptor to proper chaining with grpc.ChainUnaryInterceptor - Metrics interceptor now runs first (as intended) - All interceptors properly chained in order: metrics → auth → ratelimit - This fixes metrics not being recorded for any requests 2. Fix dashboard uptime calculation - Changed from page load time to process_start_time_seconds metric - Uptime now persists correctly across page refreshes - Uses Prometheus standard process_start_time_seconds gauge Before: Metrics showed 0 for all requests, uptime reset on refresh After: Metrics properly record all gRPC requests, uptime shows actual relay uptime
-rw-r--r--cmd/relay/main.go41
-rw-r--r--internal/metrics/dashboard.html16
2 files changed, 32 insertions, 25 deletions
diff --git a/cmd/relay/main.go b/cmd/relay/main.go
index bd50e63..457a719 100644
--- a/cmd/relay/main.go
+++ b/cmd/relay/main.go
@@ -54,7 +54,18 @@ func main() {
54 path, handler := nostrv1connect.NewNostrRelayHandler(connectHandler, connect.WithInterceptors()) 54 path, handler := nostrv1connect.NewNostrRelayHandler(connectHandler, connect.WithInterceptors())
55 mux.Handle(path, handler) 55 mux.Handle(path, handler)
56 56
57 var serverOpts []grpc.ServerOption 57 var unaryInterceptors []grpc.UnaryServerInterceptor
58 var streamInterceptors []grpc.StreamServerInterceptor
59
60 var m *metrics.Metrics
61 if cfg.Metrics.Enabled {
62 m = metrics.New(cfg.Metrics.ToMetrics())
63 unaryInterceptors = append(unaryInterceptors, metrics.UnaryServerInterceptor(m))
64 streamInterceptors = append(streamInterceptors, metrics.StreamServerInterceptor(m))
65
66 mux.Handle("/metrics", m.PrometheusHandler())
67 mux.Handle("/dashboard", m.DashboardHandler())
68 }
58 69
59 if cfg.Auth.Read.Enabled || cfg.Auth.Write.Enabled { 70 if cfg.Auth.Read.Enabled || cfg.Auth.Write.Enabled {
60 authOpts := &auth.InterceptorOptions{ 71 authOpts := &auth.InterceptorOptions{
@@ -63,30 +74,22 @@ func main() {
63 TimestampWindow: cfg.Auth.TimestampWindow, 74 TimestampWindow: cfg.Auth.TimestampWindow,
64 SkipMethods: cfg.Auth.SkipMethods, 75 SkipMethods: cfg.Auth.SkipMethods,
65 } 76 }
66 serverOpts = append(serverOpts, 77 unaryInterceptors = append(unaryInterceptors, auth.NostrUnaryInterceptor(authOpts))
67 grpc.UnaryInterceptor(auth.NostrUnaryInterceptor(authOpts)), 78 streamInterceptors = append(streamInterceptors, auth.NostrStreamInterceptor(authOpts))
68 grpc.StreamInterceptor(auth.NostrStreamInterceptor(authOpts)),
69 )
70 } 79 }
71 80
72 if cfg.RateLimit.Enabled { 81 if cfg.RateLimit.Enabled {
73 limiter := ratelimit.New(cfg.RateLimit.ToRateLimiter()) 82 limiter := ratelimit.New(cfg.RateLimit.ToRateLimiter())
74 serverOpts = append(serverOpts, 83 unaryInterceptors = append(unaryInterceptors, ratelimit.UnaryInterceptor(limiter))
75 grpc.ChainUnaryInterceptor(ratelimit.UnaryInterceptor(limiter)), 84 streamInterceptors = append(streamInterceptors, ratelimit.StreamInterceptor(limiter))
76 grpc.ChainStreamInterceptor(ratelimit.StreamInterceptor(limiter)),
77 )
78 } 85 }
79 86
80 var m *metrics.Metrics 87 var serverOpts []grpc.ServerOption
81 if cfg.Metrics.Enabled { 88 if len(unaryInterceptors) > 0 {
82 m = metrics.New(cfg.Metrics.ToMetrics()) 89 serverOpts = append(serverOpts, grpc.ChainUnaryInterceptor(unaryInterceptors...))
83 serverOpts = append(serverOpts, 90 }
84 grpc.ChainUnaryInterceptor(metrics.UnaryServerInterceptor(m)), 91 if len(streamInterceptors) > 0 {
85 grpc.ChainStreamInterceptor(metrics.StreamServerInterceptor(m)), 92 serverOpts = append(serverOpts, grpc.ChainStreamInterceptor(streamInterceptors...))
86 )
87
88 mux.Handle("/metrics", m.PrometheusHandler())
89 mux.Handle("/dashboard", m.DashboardHandler())
90 } 93 }
91 94
92 wsHandler := wshandler.NewHandler(store, subManager) 95 wsHandler := wshandler.NewHandler(store, subManager)
diff --git a/internal/metrics/dashboard.html b/internal/metrics/dashboard.html
index 800b6df..e7af48c 100644
--- a/internal/metrics/dashboard.html
+++ b/internal/metrics/dashboard.html
@@ -189,7 +189,7 @@
189 </div> 189 </div>
190 190
191 <script> 191 <script>
192 let startTime = Date.now(); 192 let processStartTime = null;
193 193
194 function parsePrometheusMetrics(text) { 194 function parsePrometheusMetrics(text) {
195 const metrics = {}; 195 const metrics = {};
@@ -294,8 +294,15 @@
294 document.getElementById('event_deletions').textContent = 294 document.getElementById('event_deletions').textContent =
295 sumMetric(metrics, `${prefix}_relay_event_deletions_total`); 295 sumMetric(metrics, `${prefix}_relay_event_deletions_total`);
296 296
297 document.getElementById('uptime').textContent = 297 const processStart = sumMetric(metrics, 'process_start_time_seconds');
298 formatUptime(Date.now() - startTime); 298 if (processStart > 0) {
299 if (!processStartTime) processStartTime = processStart;
300 const uptimeSeconds = Date.now() / 1000 - processStart;
301 document.getElementById('uptime').textContent =
302 formatUptime(uptimeSeconds * 1000);
303 } else {
304 document.getElementById('uptime').textContent = '--';
305 }
299 306
300 document.getElementById('error').innerHTML = ''; 307 document.getElementById('error').innerHTML = '';
301 } catch (error) { 308 } catch (error) {
@@ -306,9 +313,6 @@
306 313
307 updateMetrics(); 314 updateMetrics();
308 setInterval(updateMetrics, 5000); 315 setInterval(updateMetrics, 5000);
309 setInterval(() => {
310 document.getElementById('uptime').textContent = formatUptime(Date.now() - startTime);
311 }, 1000);
312 </script> 316 </script>
313</body> 317</body>
314</html> 318</html>