Add debug events to command execution

This commit is contained in:
2026-10-26 03:03:57 +00:00
parent 7a1067d387
commit f3e9326544
3 changed files with 241 additions and 15 deletions

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"strconv"
"strings"
internalcobra "gitea.maximumdirect.net/eric/CommandKit/internal/cobra"
@@ -21,18 +22,22 @@ func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outco
current := newExecution(a, ctx, streams)
if a == nil {
err := errors.New("cannot execute commands on a nil app")
return current.finalize(Failure(err.Error()).WithCause(err), nil)
return current.finalize(Failure(err.Error()).WithCause(err), nil, false)
}
if ctx == nil {
err := errors.New("invocation context must not be nil")
return current.finalize(Failure(err.Error()).WithCause(err), nil)
return current.finalize(Failure(err.Error()).WithCause(err), nil, false)
}
routedSnapshot := current.snapshot
current.route = resolveEnteredRoute(args, routedSnapshot)
current.recordRouteEvents()
parsed := current.parse(current.route.args)
current.reporter.applySelection(parsed.Reporting)
current.recordSelectionEvents(parsed)
current.submitDebugEvents()
var outcome Outcome
path := parsed.Path
handlerRan := false
if parsed.Failure != nil {
outcome = Usage(parsed.Failure.Message)
} else if current.route.kind == routeDiscovery {
@@ -59,22 +64,24 @@ func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outco
if err != nil {
outcome = classifyInputError(err)
} else {
handlerRan = true
outcome = run(current)
}
}
}
return current.finalize(outcome, path)
return current.finalize(outcome, path, handlerRan)
}
type execution struct {
app *App
ctx context.Context
streams Streams
reporter *Reporter
options commandmeta.ParseOptions
snapshot *routingSnapshot
session *internalcobra.Session
route routeResult
app *App
ctx context.Context
streams Streams
reporter *Reporter
debugEvents []string
options commandmeta.ParseOptions
snapshot *routingSnapshot
session *internalcobra.Session
route routeResult
}
type commandExecution struct {
@@ -220,7 +227,90 @@ func (current *execution) routeUsageLine() string {
return line + "\nAlias for: " + canonical
}
func (current *execution) finalize(outcome Outcome, path []string) Outcome {
func (current *execution) recordRouteEvents() {
entered := current.route.enteredPath.String()
canonical := current.route.canonicalPath.String()
switch current.route.kind {
case routeCanonical:
if entered == canonical {
if canonical == "" {
current.debugEvents = append(current.debugEvents, "selected the application root command")
} else {
current.debugEvents = append(current.debugEvents, fmt.Sprintf("selected command path %q", canonical))
}
} else {
current.debugEvents = append(current.debugEvents, fmt.Sprintf("resolved command alias %q to %q", entered, canonical))
}
case routeDiscovery:
current.debugEvents = append(current.debugEvents, fmt.Sprintf("selected discovery route for prefix %q", entered))
default:
if entered != "" {
current.debugEvents = append(current.debugEvents, fmt.Sprintf("no canonical route matched command path %q", entered))
}
}
}
func (current *execution) recordSelectionEvents(result commandmeta.Result) {
mode := "ordinary"
flagName := ""
switch result.Reporting.Mode {
case commandmeta.ReportingModeQuiet:
mode, flagName = "quiet", "quiet"
case commandmeta.ReportingModeVerbose:
mode, flagName = "verbose", "verbose"
case commandmeta.ReportingModeDebug:
mode, flagName = "debug", "debug"
}
event := "selected ordinary reporting mode"
if flagName != "" {
event = fmt.Sprintf("selected %s reporting mode", mode)
for index := len(result.FlagOccurrences) - 1; index >= 0; index-- {
occurrence := result.FlagOccurrences[index]
if occurrence.Name != flagName {
continue
}
enabled, err := strconv.ParseBool(occurrence.RawValue)
if err != nil || !enabled {
continue
}
canonical := "--" + flagName
if occurrence.Spelling != canonical {
event += fmt.Sprintf(" using %s (alias for %s)", occurrence.Spelling, canonical)
} else {
event += fmt.Sprintf(" using %s", canonical)
}
break
}
}
current.debugEvents = append(current.debugEvents, event)
if result.Reporting.ProgressSet {
state := "disabled"
if result.Reporting.Progress {
state = "enabled"
}
event := "explicitly " + state + " progress output"
for index := len(result.FlagOccurrences) - 1; index >= 0; index-- {
occurrence := result.FlagOccurrences[index]
if occurrence.Name == "progress" {
if occurrence.Spelling != "--progress" {
event += fmt.Sprintf(" using %s (alias for --progress)", occurrence.Spelling)
}
break
}
}
current.debugEvents = append(current.debugEvents, event)
}
}
func (current *execution) submitDebugEvents() {
events := current.debugEvents
current.debugEvents = nil
for _, event := range events {
_ = current.reporter.Debug(event)
}
}
func (current *execution) finalize(outcome Outcome, path []string, handlerRan bool) Outcome {
if outcome.Kind == OutcomeUsage {
details := append([]Detail(nil), outcome.Explanation.Details...)
details = append(details, func(w io.Writer) error {
@@ -233,7 +323,18 @@ func (current *execution) finalize(outcome Outcome, path []string) Outcome {
})
outcome.Explanation.Details = details
}
return current.reporter.finalize(outcome, nil)
var completion func(*Reporter)
if handlerRan {
completion = func(reporter *Reporter) {
kind := outcome.Kind
if kind == "" && outcome.Status == 0 && outcome.Domain == "" {
kind = OutcomeSuccess
}
path := current.route.canonicalPath.String()
_ = reporter.Debug(fmt.Sprintf("handler completed for %q with %s outcome and status %d", path, kind, outcome.Status))
}
}
return current.reporter.finalize(outcome, completion)
}
func (current *execution) usageText(path []string) string {