Verbosity ladder: -v / -vv / --log-level on root
byob-logging.3
logging
Problem: users want a quick "more output" knob (-v), a "full debug"
knob (-vv), AND explicit control for scripts (--log-level=info).
Picking just one frustrates the others. Env-var control
(MYTOOL_LOG=debug) is expected by anyone who's used gh or
kubectl.
Idea: three persistent flags on root (byob-command-shape.5 pattern) with a documented precedence:
--log-level=warn|info|debug— explicit, wins when set.-v/-vv— count flag:-v→ Info,-vv+ → Debug. Loses to--log-levelwhen both are passed.MYTOOL_LOG=<level>env var — loses to both flags, beats the default.- Default —
Warn. The binary is quiet unless asked otherwise.
The "flags beat env" direction is deliberately opposite to byob-config.2
(which says env > file > default for config). Logging verbosity is
per-invocation, not per-shell; a -vv on the command line should not
be silenced by a stale MYTOOL_LOG=warn in the environment.
Tradeoffs: three knobs is one more than the lean option. Users who
only want -v/-vv can ignore the other two. Scripting users want
the explicit --log-level because -vv reads like a typo in a CI
log.
Design
// pkg/cmd/root/root.go
var verbose int
var logLevel string
root.PersistentFlags().CountVarP(&verbose, "verbose", "v",
"increase log verbosity (-v=info, -vv=debug)")
root.PersistentFlags().StringVar(&logLevel, "log-level", "",
"explicit log level (warn|info|debug); overrides -v")
// In PersistentPreRunE.
// Note: cobra's CountVarP starts at 0 and has no way to express
// "explicit -v0", so verbose == 0 and "no -v passed" are the same
// state. That means env-var MYTOOL_LOG wins when no -v/-vv flag is
// present — which is the intended precedence.
lvl := slog.LevelWarn
switch {
case logLevel != "":
lvl = parseLevel(logLevel) // warn|info|debug
case verbose >= 2:
lvl = slog.LevelDebug
case verbose == 1:
lvl = slog.LevelInfo
case os.Getenv("MYTOOL_LOG") != "":
lvl = parseLevel(os.Getenv("MYTOOL_LOG"))
}