1
0
mirror of https://github.com/prometheus/alertmanager.git synced 2026-02-05 15:45:34 +01:00
Files
alertmanager/ui/web.go
Will Hegedus 5362823d7a fix: pprof debug endpoints not working with --web.route-prefix (#4698)
* Fix pprof endpoints not working with --web.route-prefix

When using --web.route-prefix or --web.external-url with a path component,
the pprof debug endpoints were not accessible. The handlers were directly
passing requests to http.DefaultServeMux without adjusting the URL path
to match what the pprof handlers expect.

For example, with --web.route-prefix=/prometheus/alertmanager, a request
to /prometheus/alertmanager/debug/pprof/ was being forwarded with the full
path intact, but http.DefaultServeMux only has handlers registered for
paths starting with /debug/pprof/.

This fix reconstructs the request path by extracting the subpath parameter
from the route and prepending /debug to it, ensuring the path matches what
http.DefaultServeMux expects. The fix also preserves trailing slashes which
are required by some pprof handlers.

Added unit tests to verify pprof endpoints work correctly both with and
without route prefix configuration.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Will Hegedus <whegedus@akamai.com>

* chore: update ui/web_test.go copyright header

Co-authored-by: Siavash Safi <git@hosted.run>
Signed-off-by: Will Hegedus <whegedus@akamai.com>

* chore: update ui/web.go copyright header

Signed-off-by: Will Hegedus <whegedus@akamai.com>

* test: Use testify for new ui/web_test.go tests

Updated based on PR comments to conform with existing testing patterns

Signed-off-by: Will Hegedus <whegedus@akamai.com>

* test: modify test for pprof handler to check response body

Signed-off-by: Will Hegedus <will@wbhegedus.me>

---------

Signed-off-by: Will Hegedus <whegedus@akamai.com>
Signed-off-by: Will Hegedus <will@wbhegedus.me>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Siavash Safi <git@hosted.run>
2025-12-03 10:59:13 -08:00

109 lines
3.2 KiB
Go

// 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 (
"fmt"
"log/slog"
"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"
"github.com/prometheus/alertmanager/asset"
)
// Register registers handlers to serve files for the web interface.
func Register(r *route.Router, reloadCh chan<- chan error, logger *slog.Logger) {
r.Get("/metrics", promhttp.Handler().ServeHTTP)
r.Get("/", func(w http.ResponseWriter, req *http.Request) {
disableCaching(w)
req.URL.Path = "/static/"
fs := http.FileServer(asset.Assets)
fs.ServeHTTP(w, req)
})
r.Get("/script.js", func(w http.ResponseWriter, req *http.Request) {
disableCaching(w)
req.URL.Path = "/static/script.js"
fs := http.FileServer(asset.Assets)
fs.ServeHTTP(w, req)
})
r.Get("/favicon.ico", func(w http.ResponseWriter, req *http.Request) {
disableCaching(w)
req.URL.Path = "/static/favicon.ico"
fs := http.FileServer(asset.Assets)
fs.ServeHTTP(w, req)
})
r.Get("/lib/*path", func(w http.ResponseWriter, req *http.Request) {
disableCaching(w)
req.URL.Path = path.Join("/static/lib", route.Param(req.Context(), "path"))
fs := http.FileServer(asset.Assets)
fs.ServeHTTP(w, req)
})
r.Post("/-/reload", func(w http.ResponseWriter, req *http.Request) {
errc := make(chan error)
defer close(errc)
reloadCh <- errc
if err := <-errc; err != nil {
http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError)
}
})
r.Get("/-/healthy", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "OK")
})
r.Head("/-/healthy", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
r.Get("/-/ready", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "OK")
})
r.Head("/-/ready", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
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) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0") // Prevent proxies from caching.
}