From d3cd4017345994cd8cef589379e9b872cfe63947 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 15 Oct 2025 13:35:24 +0100 Subject: [PATCH] Frontend service: Add `/-/fe-boot-error` endpoint to track errors starting the frontend service (#112375) * add /-/fe-boot-error endpoint to track errors starting the frontend service * use promauto package * move metric to root * check error of r.Body.Close() --- pkg/services/frontend/frontend_service.go | 47 +++++++++++++++++++++++ pkg/services/frontend/index.html | 17 +++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go index 32cea9cffc8..d1e343dede7 100644 --- a/pkg/services/frontend/frontend_service.go +++ b/pkg/services/frontend/frontend_service.go @@ -2,11 +2,13 @@ package frontend import ( "context" + "io" "net" "net/http" "time" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" @@ -25,6 +27,14 @@ import ( var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/frontend") +// Initialize metrics +var bootErrorMetric = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "grafana", + Subsystem: "frontend", + Name: "boot_errors_total", + Help: "Total number of frontend boot errors", +}) + type frontendService struct { *services.BasicService cfg *setting.Cfg @@ -117,6 +127,11 @@ func (s *frontendService) routeGet(m *web.Mux, pattern string, h ...web.Handler) m.Get(pattern, handlers...) } +func (s *frontendService) routePost(m *web.Mux, pattern string, h ...web.Handler) { + handlers := append([]web.Handler{middleware.ProvideRouteOperationName(pattern)}, h...) + m.Post(pattern, handlers...) +} + // Apply the same middleware patterns as the main HTTP server func (s *frontendService) addMiddlewares(m *web.Mux) { loggermiddleware := loggermw.Provide(s.cfg, s.features) @@ -148,6 +163,38 @@ func (s *frontendService) registerRoutes(m *web.Mux) { } }) + // Frontend boot error reporting endpoint + s.routePost(m, "/-/fe-boot-error", s.handleBootError) + // All other requests return index.html s.routeGet(m, "/*", s.index.HandleRequest) } + +// handleBootError handles frontend boot error reports +func (s *frontendService) handleBootError(w http.ResponseWriter, r *http.Request) { + // Read the request body + body, err := io.ReadAll(r.Body) + if err != nil { + s.log.Error("failed to read boot error request body", "error", err) + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + + defer func() { + if err := r.Body.Close(); err != nil { + s.log.Warn("Failed to close response body", "err", err) + } + }() + + // Increment the Prometheus counter + bootErrorMetric.Inc() + + // Log the error details + s.log.Error("frontend boot error reported", "error", body) + + // Return success response + w.WriteHeader(http.StatusOK) + if _, err := w.Write([]byte("OK")); err != nil { + s.log.Error("failed to write boot error response", "error", err) + } +} diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index 957581dc56f..34c4d4f89ed 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -169,10 +169,23 @@ // Wrap in an IIFE to avoid polluting the global scope. Intentionally global-scope properties // are explicitly assigned to the `window` object. (() => { - window.__grafana_load_failed = function(...args) { - console.error('Failed to load Grafana', ...args); + window.__grafana_load_failed = function(err) { + console.error('Failed to load Grafana', err); document.querySelector('.fs-variant-loader').classList.add('fs-hidden'); document.querySelector('.fs-variant-error').classList.remove('fs-hidden'); + + // Report the error to the backend + const errorMessage = err ? err.message : 'Unknown error'; + + fetch('/-/fe-boot-error', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: errorMessage, + }).catch(err => { + console.error('Failed to report boot error to backend: ', err); + }); }; window.onload = function() {