Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: add --log-level CLI flag #15

Merged
merged 5 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Tests

on: [pull_request]

jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22.5

- name: Test
run: go test -v ./...
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ require (
github.com/briandowns/spinner v1.23.1
github.com/fatih/color v1.17.0
github.com/jedib0t/go-pretty/v6 v6.6.0
github.com/loopholelabs/logging v0.3.0
github.com/loopholelabs/logging v0.3.1
github.com/mattn/go-isatty v0.0.20
github.com/mitchellh/mapstructure v1.5.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand All @@ -28,6 +30,7 @@ require (
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/loopholelabs/logging v0.3.0 h1:Rfo9fGdBk4nwbNdiLrVUF7JkYoC67dvGDodkRe81xF0=
github.com/loopholelabs/logging v0.3.0/go.mod h1:uRDUydiqPqKbZkb0WoQ3dfyAcJ2iOMhxdEafZssLVv0=
github.com/loopholelabs/logging v0.3.1 h1:VA9DF3WrbmvJC1uQJ/XcWgz8KWXydWwe3BdDiMbN2FY=
github.com/loopholelabs/logging v0.3.1/go.mod h1:uRDUydiqPqKbZkb0WoQ3dfyAcJ2iOMhxdEafZssLVv0=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
Expand Down
74 changes: 43 additions & 31 deletions pkg/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ type Command[T config.Config] struct {
newConfig config.New[T]
config T
setupCommands []SetupCommand[T]

format printer.Format
debug bool
logLevel types.Level

// The following io.Writer values should be used when outputting text. They
// default to os.Stdout and os.Stderr but may be changed during tests.
stdout io.Writer
stderr io.Writer
}

var (
Expand Down Expand Up @@ -72,33 +81,32 @@ func New[T config.Config](cli string, short string, long string, noargs bool, ve
version: version,
newConfig: newConfig,
setupCommands: setupCommands,
stdout: os.Stdout,
stderr: os.Stderr,
}
}

func (c *Command[T]) Execute(ctx context.Context, commandType Type) int {
var format printer.Format
var debug bool

devEnv := fmt.Sprintf("%s_DISABLE_DEV_WARNING", strings.ToUpper(replacer.Replace(c.cli)))
devWarning := fmt.Sprintf("!! WARNING: You are using a self-compiled binary which is not officially supported.\n!! To dismiss this warning, set %s=true\n\n", devEnv)

if _, ok := os.LookupEnv(devEnv); !ok {
if c.version.GitCommit() == "" || c.version.GoVersion() == "" || c.version.BuildDate() == "" || c.version.Version() == "" || c.version.Platform() == "" {
_, _ = fmt.Fprintf(os.Stderr, devWarning)
_, _ = fmt.Fprintf(c.stderr, devWarning)
}
}

err := c.runCmd(ctx, &format, &debug, commandType)
err := c.runCmd(ctx, commandType)
if err == nil {
return 0
}

// print any user specific messages first
switch format {
switch c.format {
case printer.JSON:
_, _ = fmt.Fprintf(os.Stderr, `{"error": "%s"}`, err)
_, _ = fmt.Fprintf(c.stderr, `{"error": "%s"}`, err)
default:
_, _ = fmt.Fprintf(os.Stderr, "Error: %s\n", err)
_, _ = fmt.Fprintf(c.stderr, "Error: %s\n", err)
}

logClosersLock.Lock()
Expand All @@ -118,7 +126,7 @@ func (c *Command[T]) Execute(ctx context.Context, commandType Type) int {

// runCmd adds all child commands to the root command, sets flags
// appropriately, and runs the root command.
func (c *Command[T]) runCmd(ctx context.Context, format *printer.Format, debug *bool, commandType Type) error {
func (c *Command[T]) runCmd(ctx context.Context, commandType Type) error {
c.config = c.newConfig()

configDir, err := c.config.DefaultConfigDir()
Expand Down Expand Up @@ -147,41 +155,41 @@ func (c *Command[T]) runCmd(ctx context.Context, format *printer.Format, debug *
cobra.OnInitialize(func() {
err := c.initConfig()
if err != nil {
switch *format {
switch c.format {
case printer.JSON:
_, _ = fmt.Fprintf(os.Stderr, `{"error": "%s"}`, err)
_, _ = fmt.Fprintf(c.stderr, `{"error": "%s"}`, err)
default:
_, _ = fmt.Fprintf(os.Stderr, "Error: %s\n", err)
_, _ = fmt.Fprintf(c.stderr, "Error: %s\n", err)
}

os.Exit(cmdutils.FatalErrExitCode)
}

ch.SetDebug(debug)
ch.SetDebug(&c.debug)

ch.Printer = printer.NewPrinter(format)
ch.Printer = printer.NewPrinter(&c.format)

if strings.TrimSpace(logFile) == "" {
logOutput = os.Stderr
logOutput = c.stderr
} else {
if err := os.MkdirAll(filepath.Dir(logFile), 0700); err != nil {
switch *format {
switch c.format {
case printer.JSON:
_, _ = fmt.Fprintf(os.Stderr, `{"error": "%s"}`, err)
_, _ = fmt.Fprintf(c.stderr, `{"error": "%s"}`, err)
default:
_, _ = fmt.Fprintf(os.Stderr, "Error: %s\n", err)
_, _ = fmt.Fprintf(c.stderr, "Error: %s\n", err)
}

os.Exit(cmdutils.FatalErrExitCode)
}

fileLogOutput, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0700)
if err != nil {
switch *format {
switch c.format {
case printer.JSON:
_, _ = fmt.Fprintf(os.Stderr, `{"error": "%s"}`, err)
_, _ = fmt.Fprintf(c.stderr, `{"error": "%s"}`, err)
default:
_, _ = fmt.Fprintf(os.Stderr, "Error: %s\n", err)
_, _ = fmt.Fprintf(c.stderr, "Error: %s\n", err)
}

os.Exit(cmdutils.FatalErrExitCode)
Expand All @@ -192,24 +200,19 @@ func (c *Command[T]) runCmd(ctx context.Context, format *printer.Format, debug *
logClosers = append(logClosers, fileLogOutput.Close)

if ch.Debug() {
logOutput = io.MultiWriter(fileLogOutput, os.Stderr)
logOutput = io.MultiWriter(fileLogOutput, c.stderr)
} else {
logOutput = fileLogOutput
}
}

switch *format {
switch c.format {
case printer.JSON:
ch.Logger = logging.New(logging.Zerolog, strings.ToLower(c.cli), logOutput)
default:
ch.Logger = logging.New(logging.Slog, strings.ToLower(c.cli), logOutput)
}

if ch.Debug() {
ch.Logger.SetLevel(types.TraceLevel)
} else {
ch.Logger.SetLevel(types.InfoLevel)
}
ch.Logger.SetLevel(c.logLevel)
})

c.command.SilenceUsage = true
Expand All @@ -222,19 +225,28 @@ func (c *Command[T]) runCmd(ctx context.Context, format *printer.Format, debug *

c.config.RootPersistentFlags(c.command.PersistentFlags())

c.command.PersistentFlags().VarP(printer.NewFormatValue(printer.Human, format), "format", "f", "Show output in a specific format. Possible values: [human, json]")
c.command.PersistentFlags().VarP(printer.NewFormatValue(printer.Human, &c.format), "format", "f", "Show output in a specific format. Possible values: [human, json]")
if err = viper.BindPFlag("format", c.command.PersistentFlags().Lookup("format")); err != nil {
return err
}
_ = c.command.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"human", "json"}, cobra.ShellCompDirectiveDefault
})

c.command.PersistentFlags().BoolVar(debug, "debug", false, "Enable debug mode")
c.command.PersistentFlags().BoolVar(&c.debug, "debug", false, "Enable debug mode")
if err = viper.BindPFlag("debug", c.command.PersistentFlags().Lookup("debug")); err != nil {
return err
}

c.logLevel = types.InfoLevel
c.command.PersistentFlags().VarP(&c.logLevel, "log-level", "", "")
if err = viper.BindPFlag("log-level", c.command.PersistentFlags().Lookup("debug")); err != nil {
return err
}
_ = c.command.RegisterFlagCompletionFunc("log-level", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"fatal", "error", "warn", "info", "debug", "trace"}, cobra.ShellCompDirectiveDefault
})

c.command.PersistentFlags().BoolVar(&color.NoColor, "no-color", false, "Disable color output")
if err = viper.BindPFlag("no-color", c.command.PersistentFlags().Lookup("no-color")); err != nil {
return err
Expand Down
Loading