diff --git a/ui/web.go b/ui/web.go index 3b81135237..4c6d189463 100644 --- a/ui/web.go +++ b/ui/web.go @@ -1,4 +1,4 @@ -// Copyright 2015 Prometheus Team +// Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -19,6 +19,7 @@ import ( "net/http" _ "net/http/pprof" // Comment this line to disable pprof endpoint. "path" + "strings" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/route" @@ -87,8 +88,17 @@ func Register(r *route.Router, reloadCh chan<- chan error, logger *slog.Logger) w.WriteHeader(http.StatusOK) }) - r.Get("/debug/*subpath", http.DefaultServeMux.ServeHTTP) - r.Post("/debug/*subpath", http.DefaultServeMux.ServeHTTP) + debugHandlerFunc := func(w http.ResponseWriter, req *http.Request) { + subpath := route.Param(req.Context(), "subpath") + req.URL.Path = path.Join("/debug", subpath) + // path.Join removes trailing slashes, but some pprof handlers expect them. + if strings.HasSuffix(subpath, "/") && !strings.HasSuffix(req.URL.Path, "/") { + req.URL.Path += "/" + } + http.DefaultServeMux.ServeHTTP(w, req) + } + r.Get("/debug/*subpath", debugHandlerFunc) + r.Post("/debug/*subpath", debugHandlerFunc) } func disableCaching(w http.ResponseWriter) { diff --git a/ui/web_test.go b/ui/web_test.go new file mode 100644 index 0000000000..e6293d1834 --- /dev/null +++ b/ui/web_test.go @@ -0,0 +1,61 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ui + +import ( + "log/slog" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/prometheus/common/route" + "github.com/stretchr/testify/require" +) + +func TestDebugHandlersWithRoutePrefix(t *testing.T) { + logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) + reloadCh := make(chan chan error) + + // Test with route prefix + routePrefix := "/prometheus/alertmanager" + router := route.New().WithPrefix(routePrefix) + Register(router, reloadCh, logger) + + // Test GET request to pprof index (note: pprof index returns text/html) + req := httptest.NewRequest("GET", routePrefix+"/debug/pprof/", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Code) + require.Contains(t, w.Body.String(), "/debug/pprof/", "pprof page did not load with expected content when using a route prefix") + + // Test GET request to pprof heap endpoint + req = httptest.NewRequest("GET", routePrefix+"/debug/pprof/heap", nil) + w = httptest.NewRecorder() + router.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Code) + + // Test without route prefix (should also work) + router2 := route.New() + Register(router2, reloadCh, logger) + + req = httptest.NewRequest("GET", "/debug/pprof/", nil) + w = httptest.NewRecorder() + router2.ServeHTTP(w, req) + + require.Equal(t, http.StatusOK, w.Code) + require.Contains(t, w.Body.String(), "/debug/pprof/", "pprof page did not load with expected content") +}