// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause package tsweb import ( "expvar" "fmt" "html" "io" "net/http" "net/http/pprof" "net/url" "os" "runtime" "tailscale.com/tsweb/promvarz" "tailscale.com/tsweb/varz" "tailscale.com/version" ) // DebugHandler is an http.Handler that serves a debugging "homepage", // and provides helpers to register more debug endpoints and reports. // // The rendered page consists of three sections: informational // key/value pairs, links to other pages, and additional // program-specific HTML. Callers can add to these sections using the // KV, URL and Section helpers respectively. // // Additionally, the Handle method offers a shorthand for correctly // registering debug handlers and cross-linking them from /debug/. type DebugHandler struct { mux *http.ServeMux // where this handler is registered kvs []func(io.Writer) // output one
  • ...
  • each, see KV() urls []string // one
  • ...
  • block with link each sections []func(io.Writer, *http.Request) // invoked in registration order prior to outputting } // Debugger returns the DebugHandler registered on mux at /debug/, // creating it if necessary. func Debugger(mux *http.ServeMux) *DebugHandler { h, pat := mux.Handler(&http.Request{URL: &url.URL{Path: "/debug/"}}) if d, ok := h.(*DebugHandler); ok && pat == "/debug/" { return d } ret := &DebugHandler{ mux: mux, } mux.Handle("/debug/", ret) // Register this one directly on mux, rather than using // ret.URL/etc, as we don't need another line of output on the // index page. The /pprof/ index already covers it. mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) ret.KVFunc("Uptime", func() any { return varz.Uptime() }) ret.KV("Version", version.Long()) ret.Handle("vars", "Metrics (Go)", expvar.Handler()) ret.Handle("varz", "Metrics (Prometheus)", http.HandlerFunc(promvarz.Handler)) ret.Handle("pprof/", "pprof", http.HandlerFunc(pprof.Index)) ret.URL("/debug/pprof/goroutine?debug=1", "Goroutines (collapsed)") ret.URL("/debug/pprof/goroutine?debug=2", "Goroutines (full)") ret.Handle("gc", "force GC", http.HandlerFunc(gcHandler)) hostname, err := os.Hostname() if err == nil { ret.KV("Machine", hostname) } return ret } // ServeHTTP implements http.Handler. func (d *DebugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !AllowDebugAccess(r) { http.Error(w, "debug access denied", http.StatusForbidden) return } if r.URL.Path != "/debug/" { // Sub-handlers are handled by the parent mux directly. http.NotFound(w, r) return } f := func(format string, args ...any) { fmt.Fprintf(w, format, args...) } f("

    %s debug