diff --git a/proxy/cmd/proxy/cmd/debug.go b/proxy/cmd/proxy/cmd/debug.go index 86172d78b..59f7a6b65 100644 --- a/proxy/cmd/proxy/cmd/debug.go +++ b/proxy/cmd/proxy/cmd/debug.go @@ -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 ", + Use: "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) diff --git a/proxy/internal/debug/client.go b/proxy/internal/debug/client.go index 6b78a9b8a..d43a84bbe 100644 --- a/proxy/internal/debug/client.go +++ b/proxy/internal/debug/client.go @@ -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. diff --git a/proxy/internal/debug/handler.go b/proxy/internal/debug/handler.go index f7b1fa87c..032f8b196 100644 --- a/proxy/internal/debug/handler.go +++ b/proxy/internal/debug/handler.go @@ -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) diff --git a/proxy/internal/debug/templates/health.html b/proxy/internal/debug/templates/health.html index f584f8357..190b698c9 100644 --- a/proxy/internal/debug/templates/health.html +++ b/proxy/internal/debug/templates/health.html @@ -6,8 +6,33 @@ -

OK

+

{{.Status}}

Uptime: {{.Uptime}}

+

Management Connected: {{.ManagementReady}}

+

All Clients Healthy: {{.AllClientsHealthy}}

+ {{if .Clients}} +

Clients

+ + + + + + + + + + {{range $id, $c := .Clients}} + + + + + + + + + {{end}} +
Account IDHealthyManagementSignalRelaysError
{{$id}}{{$c.Healthy}}{{$c.ManagementConnected}}{{$c.SignalConnected}}{{$c.RelaysConnected}}/{{$c.RelaysTotal}}{{$c.Error}}
+ {{end}}

← Back

diff --git a/proxy/server.go b/proxy/server.go index c3c6ca218..780f9a8d4 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -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 {