Add health status to debug

This commit is contained in:
Viktor Liu
2026-02-08 21:04:14 +08:00
parent 99e6b1eda4
commit 51e63c246b
5 changed files with 117 additions and 11 deletions

View File

@@ -68,8 +68,14 @@ var debugPingCmd = &cobra.Command{
SilenceUsage: true,
}
var debugLogCmd = &cobra.Command{
Use: "log",
Short: "Manage client logging",
Long: "Commands to manage logging settings for a client connected through the proxy.",
}
var debugLogLevelCmd = &cobra.Command{
Use: "loglevel <account-id> <level>",
Use: "level <account-id> <level>",
Short: "Set client log level",
Long: "Set the log level for a client (trace, debug, info, warn, error).",
Args: cobra.ExactArgs(2),
@@ -109,7 +115,8 @@ func init() {
debugCmd.AddCommand(debugStatusCmd)
debugCmd.AddCommand(debugSyncCmd)
debugCmd.AddCommand(debugPingCmd)
debugCmd.AddCommand(debugLogLevelCmd)
debugLogCmd.AddCommand(debugLogLevelCmd)
debugCmd.AddCommand(debugLogCmd)
debugCmd.AddCommand(debugStartCmd)
debugCmd.AddCommand(debugStopCmd)

View File

@@ -53,6 +53,48 @@ func (c *Client) Health(ctx context.Context) error {
func (c *Client) printHealth(data map[string]any) {
_, _ = fmt.Fprintf(c.out, "Status: %v\n", data["status"])
_, _ = fmt.Fprintf(c.out, "Uptime: %v\n", data["uptime"])
_, _ = fmt.Fprintf(c.out, "Management Connected: %s\n", boolIcon(data["management_connected"]))
_, _ = fmt.Fprintf(c.out, "All Clients Healthy: %s\n", boolIcon(data["all_clients_healthy"]))
clients, ok := data["clients"].(map[string]any)
if !ok || len(clients) == 0 {
return
}
_, _ = fmt.Fprintf(c.out, "\n%-38s %-9s %-7s %-8s %s\n", "ACCOUNT ID", "HEALTHY", "MGMT", "SIGNAL", "RELAYS")
_, _ = fmt.Fprintln(c.out, strings.Repeat("-", 80))
for accountID, v := range clients {
ch, ok := v.(map[string]any)
if !ok {
continue
}
healthy := boolIcon(ch["healthy"])
mgmt := boolIcon(ch["management_connected"])
signal := boolIcon(ch["signal_connected"])
relaysConn, _ := ch["relays_connected"].(float64)
relaysTotal, _ := ch["relays_total"].(float64)
relays := fmt.Sprintf("%d/%d", int(relaysConn), int(relaysTotal))
_, _ = fmt.Fprintf(c.out, "%-38s %-9s %-7s %-8s %s", accountID, healthy, mgmt, signal, relays)
if errMsg, ok := ch["error"].(string); ok && errMsg != "" {
_, _ = fmt.Fprintf(c.out, " (%s)", errMsg)
}
_, _ = fmt.Fprintln(c.out)
}
}
func boolIcon(v any) string {
b, ok := v.(bool)
if !ok {
return "?"
}
if b {
return "yes"
}
return "no"
}
// ListClients fetches the list of all clients.

View File

@@ -18,6 +18,7 @@ import (
nbembed "github.com/netbirdio/netbird/client/embed"
nbstatus "github.com/netbirdio/netbird/client/status"
"github.com/netbirdio/netbird/proxy/internal/health"
"github.com/netbirdio/netbird/proxy/internal/roundtrip"
"github.com/netbirdio/netbird/proxy/internal/types"
"github.com/netbirdio/netbird/version"
@@ -52,9 +53,17 @@ type clientProvider interface {
ListClientsForDebug() map[types.AccountID]roundtrip.ClientDebugInfo
}
// healthChecker provides health probe state.
type healthChecker interface {
ReadinessProbe() bool
StartupProbe(ctx context.Context) bool
CheckClientsConnected(ctx context.Context) (bool, map[types.AccountID]health.ClientHealth)
}
// Handler provides HTTP debug endpoints.
type Handler struct {
provider clientProvider
health healthChecker
logger *log.Logger
startTime time.Time
templates *template.Template
@@ -62,12 +71,13 @@ type Handler struct {
}
// NewHandler creates a new debug handler.
func NewHandler(provider clientProvider, logger *log.Logger) *Handler {
func NewHandler(provider clientProvider, healthChecker healthChecker, logger *log.Logger) *Handler {
if logger == nil {
logger = log.StandardLogger()
}
h := &Handler{
provider: provider,
health: healthChecker,
logger: logger,
startTime: time.Now(),
}
@@ -547,20 +557,41 @@ func (h *Handler) handleClientStop(w http.ResponseWriter, r *http.Request, accou
}
type healthData struct {
Uptime string
Uptime string
Status string
ManagementReady bool
AllClientsHealthy bool
Clients map[types.AccountID]health.ClientHealth
}
func (h *Handler) handleHealth(w http.ResponseWriter, _ *http.Request, wantJSON bool) {
func (h *Handler) handleHealth(w http.ResponseWriter, r *http.Request, wantJSON bool) {
uptime := time.Since(h.startTime).Round(10 * time.Millisecond).String()
ready := h.health.ReadinessProbe()
allHealthy, clientHealth := h.health.CheckClientsConnected(r.Context())
status := "ok"
if !ready || !allHealthy {
status = "degraded"
}
if wantJSON {
h.writeJSON(w, map[string]interface{}{
"status": "ok",
"uptime": time.Since(h.startTime).Round(10 * time.Millisecond).String(),
"status": status,
"uptime": uptime,
"management_connected": ready,
"all_clients_healthy": allHealthy,
"clients": clientHealth,
})
return
}
data := healthData{
Uptime: time.Since(h.startTime).Round(time.Second).String(),
Uptime: time.Since(h.startTime).Round(time.Second).String(),
Status: status,
ManagementReady: ready,
AllClientsHealthy: allHealthy,
Clients: clientHealth,
}
h.renderTemplate(w, "health", data)

View File

@@ -6,8 +6,33 @@
<style>{{template "style"}}</style>
</head>
<body>
<h1>OK</h1>
<h1>{{.Status}}</h1>
<p>Uptime: {{.Uptime}}</p>
<p>Management Connected: {{.ManagementReady}}</p>
<p>All Clients Healthy: {{.AllClientsHealthy}}</p>
{{if .Clients}}
<h2>Clients</h2>
<table>
<tr>
<th>Account ID</th>
<th>Healthy</th>
<th>Management</th>
<th>Signal</th>
<th>Relays</th>
<th>Error</th>
</tr>
{{range $id, $c := .Clients}}
<tr>
<td>{{$id}}</td>
<td>{{$c.Healthy}}</td>
<td>{{$c.ManagementConnected}}</td>
<td>{{$c.SignalConnected}}</td>
<td>{{$c.RelaysConnected}}/{{$c.RelaysTotal}}</td>
<td>{{$c.Error}}</td>
</tr>
{{end}}
</table>
{{end}}
<p><a href="/debug">&larr; Back</a></p>
</body>
</html>

View File

@@ -230,9 +230,11 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
// Configure Access logs to management server.
accessLog := accesslog.NewLogger(s.mgmtClient, s.Logger, s.TrustedProxies)
s.healthChecker = health.NewChecker(s.Logger, s.netbird)
if s.DebugEndpointEnabled {
debugAddr := debugEndpointAddr(s.DebugEndpointAddress)
debugHandler := debug.NewHandler(s.netbird, s.Logger)
debugHandler := debug.NewHandler(s.netbird, s.healthChecker, s.Logger)
s.debug = &http.Server{
Addr: debugAddr,
Handler: debugHandler,
@@ -255,7 +257,6 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
if healthAddr == "" {
healthAddr = "localhost:8080"
}
s.healthChecker = health.NewChecker(s.Logger, s.netbird)
s.healthServer = health.NewServer(healthAddr, s.healthChecker, s.Logger)
healthListener, err := net.Listen("tcp", healthAddr)
if err != nil {