mirror of
https://github.com/caddyserver/caddy.git
synced 2026-07-10 19:04:22 +00:00
Merge cebfb859f6 into 13a4c3f43c
This commit is contained in:
commit
d366b16205
4 changed files with 175 additions and 4 deletions
56
admin.go
56
admin.go
|
|
@ -34,6 +34,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -263,6 +264,7 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, ctx
|
|||
addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig))
|
||||
addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID))
|
||||
addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop))
|
||||
addRoute("/status", handlerLabel, AdminHandlerFunc(handleStatus))
|
||||
|
||||
// register debugging endpoints
|
||||
addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index))
|
||||
|
|
@ -1483,3 +1485,57 @@ var (
|
|||
localAdminServer, remoteAdminServer *http.Server
|
||||
identityCertCache *certmagic.Cache
|
||||
)
|
||||
|
||||
// handleStatus returns a snapshot of the current state of the Caddy instance.
|
||||
func handleStatus(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodGet {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusMethodNotAllowed,
|
||||
Err: fmt.Errorf("method not allowed"),
|
||||
}
|
||||
}
|
||||
|
||||
ctx := ActiveContext()
|
||||
_, fullVer := Version()
|
||||
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
|
||||
// Build the base status structure
|
||||
statusObj := map[string]any{
|
||||
"version": fullVer,
|
||||
"uptime_secs": time.Since(ProcessStartTime).Seconds(),
|
||||
"memory": map[string]any{
|
||||
"allocated_bytes": mem.Alloc,
|
||||
"system_bytes": mem.Sys,
|
||||
},
|
||||
"goroutines": runtime.NumGoroutine(),
|
||||
"apps": map[string]any{},
|
||||
}
|
||||
|
||||
// Iterate through running apps if a config is active
|
||||
if ctx.cfg != nil {
|
||||
for appName := range ctx.cfg.AppsRaw {
|
||||
appIntf, err := ctx.App(appName)
|
||||
if err != nil {
|
||||
continue // Ignore if the app instance cannot be retrieved
|
||||
}
|
||||
|
||||
// Dynamically check if the App implements the StatusReporter interface
|
||||
if reporter, ok := appIntf.(StatusReporter); ok {
|
||||
appStatus, err := reporter.Status()
|
||||
if err == nil {
|
||||
statusObj["apps"].(map[string]any)[appName] = appStatus
|
||||
} else {
|
||||
statusObj["apps"].(map[string]any)[appName] = map[string]any{"error": err.Error()}
|
||||
}
|
||||
} else {
|
||||
// The app exists but does not support advanced status reporting yet
|
||||
statusObj["apps"].(map[string]any)[appName] = map[string]any{"status": "running"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
return json.NewEncoder(w).Encode(statusObj)
|
||||
}
|
||||
|
|
|
|||
9
caddy.go
9
caddy.go
|
|
@ -100,6 +100,15 @@ type App interface {
|
|||
Stop() error
|
||||
}
|
||||
|
||||
// StatusReporter can be implemented by Apps or Modules that wish
|
||||
// to expose runtime status information via the /status admin endpoint.
|
||||
type StatusReporter interface {
|
||||
Status() (any, error)
|
||||
}
|
||||
|
||||
// ProcessStartTime records when the Caddy process started.
|
||||
var ProcessStartTime = time.Now()
|
||||
|
||||
// Run runs the given config, replacing any existing config.
|
||||
func Run(cfg *Config) error {
|
||||
cfgJSON, err := json.Marshal(cfg)
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ func TestCommandsAreAvailable(t *testing.T) {
|
|||
t.Fatal("default factory failed to build")
|
||||
}
|
||||
|
||||
// check that the default factory has 17 commands; it doesn't
|
||||
// check that the default factory has 18 commands; it doesn't
|
||||
// include the commands registered through calls to init in
|
||||
// other packages
|
||||
cmds := Commands()
|
||||
if len(cmds) != 17 {
|
||||
t.Errorf("expected 17 commands, got %d", len(cmds))
|
||||
if len(cmds) != 18 {
|
||||
t.Errorf("expected 18 commands, got %d", len(cmds))
|
||||
}
|
||||
|
||||
commandNames := slices.Collect(maps.Keys(cmds))
|
||||
|
|
@ -30,7 +30,7 @@ func TestCommandsAreAvailable(t *testing.T) {
|
|||
"adapt", "add-package", "build-info", "completion",
|
||||
"environ", "fmt", "list-modules", "manpage",
|
||||
"reload", "remove-package", "run", "start",
|
||||
"stop", "storage", "upgrade", "validate", "version",
|
||||
"status", "stop", "storage", "upgrade", "validate", "version",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expectedCommandNames, commandNames) {
|
||||
|
|
|
|||
106
cmd/status.go
Normal file
106
cmd/status.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package caddycmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterCommand(Command{
|
||||
Name: "status",
|
||||
Func: cmdStatus,
|
||||
Usage: "[--json] [--address <admin-api-address>] [--config <path>] [--adapter <name>]",
|
||||
Short: "Prints the status of the running Caddy instance",
|
||||
Flags: func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("status", flag.ExitOnError)
|
||||
fs.Bool("json", false, "Output raw JSON instead of human-readable text")
|
||||
fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default")
|
||||
fs.String("config", "", "Configuration file")
|
||||
fs.String("adapter", "", "Name of config adapter to apply")
|
||||
return fs
|
||||
}(),
|
||||
})
|
||||
}
|
||||
|
||||
// cmdStatus implements the 'caddy status' command.
|
||||
func cmdStatus(fl Flags) (int, error) {
|
||||
useJSON := fl.Bool("json")
|
||||
addr := fl.String("address")
|
||||
cfgFile := fl.String("config")
|
||||
cfgAdapter := fl.String("adapter")
|
||||
|
||||
// Determine the admin API address based on provided flags or defaults
|
||||
adminAddr, err := DetermineAdminAPIAddress(cfgFile, nil, cfgAdapter, addr)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("could not determine admin API address: %v", err)
|
||||
}
|
||||
|
||||
resp, err := AdminAPIRequest(adminAddr, http.MethodGet, "/status", nil, nil)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to query status: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
// Strictly output raw JSON to stdout for CLI pipelines (e.g., jq)
|
||||
if useJSON {
|
||||
os.Stdout.Write(body)
|
||||
fmt.Println()
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
var status map[string]any
|
||||
if err := json.Unmarshal(body, &status); err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to parse status JSON: %v\nRaw output: %s", err, string(body))
|
||||
}
|
||||
|
||||
// Format output to be human-readable
|
||||
fmt.Printf("Caddy %v\n", status["version"])
|
||||
fmt.Println("Status: Running")
|
||||
|
||||
if uptime, ok := status["uptime_secs"].(float64); ok {
|
||||
u := int64(uptime)
|
||||
h := u / 3600
|
||||
m := (u % 3600) / 60
|
||||
s := u % 60
|
||||
if h > 0 {
|
||||
fmt.Printf("Uptime: %dh %dm %ds\n", h, m, s)
|
||||
} else if m > 0 {
|
||||
fmt.Printf("Uptime: %dm %ds\n", m, s)
|
||||
} else {
|
||||
fmt.Printf("Uptime: %ds\n", s)
|
||||
}
|
||||
}
|
||||
|
||||
if apps, ok := status["apps"].(map[string]any); ok && len(apps) > 0 {
|
||||
var appNames []string
|
||||
for appName := range apps {
|
||||
appNames = append(appNames, appName)
|
||||
}
|
||||
fmt.Printf("\nRunning apps: %s\n", strings.Join(appNames, ", "))
|
||||
} else {
|
||||
fmt.Printf("\nRunning apps: none\n")
|
||||
}
|
||||
|
||||
fmt.Println("\nMemory")
|
||||
if mem, ok := status["memory"].(map[string]any); ok {
|
||||
allocMB := mem["allocated_bytes"].(float64) / 1024 / 1024
|
||||
sysMB := mem["system_bytes"].(float64) / 1024 / 1024
|
||||
fmt.Printf("Allocated: %.0f MB\nSystem: %.0f MB\n", allocMB, sysMB)
|
||||
}
|
||||
|
||||
fmt.Printf("Goroutines: %v\n", status["goroutines"])
|
||||
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue