Skip to content

Commit 03cd63c

Browse files
feat(logging): add log-level flag and debug logging with test and linter fixes
- Add `--log-level` flag to root.go with debug, info, warn, error levels - Implement log level handling in root.go and cli.go with type assertion checks - Enhance logger.go with custom `zap.Config` and ISO8601 timestamps - Add Debugf logging in fs.go `RemoveBinary` for constructed binary path - Update mock files to include changes and accommodate test updates - Add .gitattributes file to manage line endings - Upgrade `wsl` linter to `wsl_v5` - Update mockery configuration for mockery version 3.3.2
1 parent 6d4d000 commit 03cd63c

File tree

13 files changed

+534
-140
lines changed

13 files changed

+534
-140
lines changed

.gitattributes

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Normalize all text files to LF line endings
2+
* text=auto eol=lf
3+
4+
# Explicitly set Go-related text files to LF
5+
*.go text eol=lf
6+
*.mod text eol=lf
7+
*.sum text eol=lf
8+
*.work text eol=lf
9+
*.md text eol=lf
10+
*.txt text eol=lf
11+
*.yaml text eol=lf
12+
*.yml text eol=lf
13+
*.json text eol=lf
14+
*.proto text eol=lf
15+
16+
# Treat configuration and script files as text with LF
17+
*.toml text eol=lf
18+
*.ini text eol=lf
19+
*.sh text eol=lf
20+
Dockerfile text eol=lf
21+
*.dockerignore text eol=lf
22+
*.gitignore text eol=lf
23+
*.gitattributes text eol=lf
24+
25+
# Treat binary files as binary (no line-ending conversion or diff)
26+
*.png binary
27+
*.jpg binary
28+
*.jpeg binary
29+
*.gif binary
30+
*.ico binary
31+
*.pdf binary
32+
*.zip binary
33+
*.tar binary
34+
*.gz binary
35+
*.exe binary
36+
*.dll binary
37+
*.so binary
38+
*.db binary
39+
*.sqlite binary
40+
41+
# Ensure generated Go binaries or test caches are treated as binary
42+
*.out binary
43+
*.test binary

.golangci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ linters:
5050
- usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library.
5151
- usetesting # Reports uses of functions with replacement inside the testing package.
5252
- whitespace # Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc.
53-
- wsl # Add or remove empty lines.
53+
- wsl_v5 # Add or remove empty lines.
5454
##################################################################################################
5555
# Enabled linters that require manual issue resolution
5656
- asasalint # Check for pass []any as any in variadic func(...any).

.mockery.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
# go-remove: https://github.com/nicholas-fedor/go-remove/ #
66
# Mockery: https://vektra.github.io/mockery/latest/ #
77
# Configuration: https://vektra.github.io/mockery/latest/configuration #
8-
# Mockery Version: 2.53 #
8+
# Mockery Version: 3.3.2 #
99
# #
1010
######################################################################################################
1111

1212
######################################################################################################
1313
# Mockery Usage #
1414
# #
1515
# Install Mockery: #
16-
# go install github.com/vektra/mockery/v2@v2.53.3 #
16+
# go install github.com/vektra/mockery/v3@v3.3.2 #
1717
# #
1818
# Run CLI command from root: #
1919
# mockery #
@@ -65,7 +65,7 @@
6565
# Disable all warnings for deprecated behavior.
6666
# When true, suppresses deprecation messages (e.g., for quiet, with-expecter in v3).
6767
# Default: false
68-
disable-deprecation-warnings: true
68+
# disable-deprecation-warnings: true
6969

7070
# Selectively disable specific deprecation warnings by name.
7171
# Use names from deprecation-name in log messages (e.g., "quiet", "with-expecter").
@@ -142,7 +142,7 @@ recursive: true
142142
# Generate testify expecter methods (e.g., EXPECT()) for mocks when true.
143143
# Enhances compatibility with github.com/stretchr/testify; v3 deprecation: permanently enabled.
144144
# Default: false
145-
with-expecter: true
145+
# with-expecter: true
146146

147147
# Specify build tags for the generated mock files.
148148
# Matches Go’s build constraint syntax (e.g., "// +build tag1,tag2").
@@ -157,7 +157,7 @@ with-expecter: true
157157

158158
# Package name for generated mock files.
159159
# Default: "{{.PackageName}}"
160-
outpkg: mocks
160+
pkgname: "mocks"
161161

162162
# Treat variadic parameters as a single slice when false, rather than unrolling them.
163163
# Affects how variadic methods are mocked (e.g., func(...int) vs. func([]int)).

cmd/root.go

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
package cmd
2020

2121
import (
22+
"errors"
2223
"fmt"
2324
"os"
2425

2526
"github.com/spf13/cobra"
27+
"go.uber.org/zap"
28+
"go.uber.org/zap/zapcore"
2629

2730
"github.com/nicholas-fedor/go-remove/internal/cli"
2831
"github.com/nicholas-fedor/go-remove/internal/fs"
2932
"github.com/nicholas-fedor/go-remove/internal/logger"
3033
)
3134

35+
// ErrInvalidLoggerType indicates that the logger is not of the expected *ZapLogger type.
36+
var ErrInvalidLoggerType = errors.New("logger is not a *ZapLogger")
37+
3238
// rootCmd defines the root command for go-remove.
3339
var rootCmd = &cobra.Command{
3440
Use: "go-remove [binary]",
@@ -49,11 +55,45 @@ var rootCmd = &cobra.Command{
4955
// Extract flag values to configure CLI behavior; defaults to TUI mode if no binary is given.
5056
verbose, _ := cmd.Flags().GetBool("verbose")
5157
goroot, _ := cmd.Flags().GetBool("goroot")
58+
logLevel, _ := cmd.Flags().GetString("log-level")
5259
config := cli.Config{
53-
Binary: "",
54-
Verbose: verbose,
55-
Goroot: goroot,
56-
Help: false, // Cobra manages help output automatically
60+
Binary: "",
61+
Verbose: verbose,
62+
Goroot: goroot,
63+
Help: false, // Cobra manages help output automatically
64+
LogLevel: logLevel,
65+
}
66+
67+
// Set log level based on config if verbose mode is enabled.
68+
if verbose {
69+
zapLogger, ok := log.(*logger.ZapLogger)
70+
if !ok {
71+
return fmt.Errorf(
72+
"failed to set log level: %w with type %T",
73+
ErrInvalidLoggerType,
74+
log,
75+
)
76+
}
77+
switch logLevel {
78+
case "debug":
79+
zapLogger.Logger = zapLogger.WithOptions(
80+
zap.IncreaseLevel(zapcore.DebugLevel),
81+
)
82+
case "warn":
83+
zapLogger.Logger = zapLogger.WithOptions(
84+
zap.IncreaseLevel(zapcore.WarnLevel),
85+
)
86+
case "error":
87+
zapLogger.Logger = zapLogger.WithOptions(
88+
zap.IncreaseLevel(zapcore.ErrorLevel),
89+
)
90+
default:
91+
zapLogger.Logger = zapLogger.WithOptions(
92+
zap.IncreaseLevel(zapcore.InfoLevel),
93+
)
94+
}
95+
log = zapLogger // Update the logger in deps
96+
deps.Logger = log
5797
}
5898

5999
// If a binary name is provided as an argument, run in direct removal mode.
@@ -77,6 +117,7 @@ var rootCmd = &cobra.Command{
77117
func init() {
78118
rootCmd.Flags().BoolP("verbose", "v", false, "Enable verbose output")
79119
rootCmd.Flags().BoolP("goroot", "", false, "Target GOROOT/bin instead of GOBIN or GOPATH/bin")
120+
rootCmd.Flags().StringP("log-level", "l", "info", "Set log level (debug, info, warn, error)")
80121
}
81122

82123
// Execute runs the root command and handles any execution errors.

cmd/root_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestRootCommand(t *testing.T) {
3636
{
3737
name: "help flag",
3838
args: []string{"-h"},
39-
wantStderr: "A tool to remove Go binaries\n\nUsage:\n go-remove [binary] [flags]\n\nFlags:\n --goroot Target GOROOT/bin instead of GOBIN or GOPATH/bin\n -h, --help help for go-remove\n -v, --verbose Enable verbose output\n",
39+
wantStderr: "A tool to remove Go binaries\n\nUsage:\n go-remove [binary] [flags]\n\nFlags:\n --goroot Target GOROOT/bin instead of GOBIN or GOPATH/bin\n -h, --help help for go-remove\n -l, --log-level string Set log level (debug, info, warn, error) (default \"info\")\n -v, --verbose Enable verbose output\n",
4040
wantErr: false,
4141
},
4242
}
@@ -65,7 +65,6 @@ func TestRootCommand(t *testing.T) {
6565
w.Close()
6666

6767
var buf bytes.Buffer
68-
6968
buf.ReadFrom(r)
7069
gotStderr := buf.String()
7170

internal/cli/cli.go

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
package cli
2020

2121
import (
22+
"errors"
2223
"fmt"
24+
"os"
25+
26+
"go.uber.org/zap"
27+
"go.uber.org/zap/zapcore"
2328

2429
"github.com/nicholas-fedor/go-remove/internal/fs"
2530
"github.com/nicholas-fedor/go-remove/internal/logger"
2631
)
2732

33+
// ErrInvalidLoggerType indicates that the logger is not of the expected *ZapLogger type.
34+
var ErrInvalidLoggerType = errors.New("logger is not a *ZapLogger")
35+
2836
// Config holds command-line configuration options.
2937
type Config struct {
30-
Binary string // Binary name to remove; empty for TUI mode
31-
Verbose bool // Enable verbose logging
32-
Goroot bool // Use GOROOT/bin instead of GOBIN or GOPATH/bin
33-
Help bool // Show help; managed by Cobra
38+
Binary string // Binary name to remove; empty for TUI mode
39+
Verbose bool // Enable verbose logging
40+
Goroot bool // Use GOROOT/bin instead of GOBIN or GOPATH/bin
41+
Help bool // Show help; managed by Cobra
42+
LogLevel string // Log level (debug, info, warn, error)
3443
}
3544

3645
// Dependencies holds runtime dependencies for CLI execution.
@@ -43,10 +52,31 @@ type Dependencies struct {
4352
func Run(deps Dependencies, config Config) error {
4453
log := deps.Logger
4554

55+
// Set log level based on config if verbose mode is enabled.
56+
if config.Verbose {
57+
zapLogger, ok := log.(*logger.ZapLogger)
58+
if !ok {
59+
return fmt.Errorf("failed to set log level: %w with type %T", ErrInvalidLoggerType, log)
60+
}
61+
62+
switch config.LogLevel {
63+
case "debug":
64+
zapLogger.Logger = zapLogger.WithOptions(zap.IncreaseLevel(zapcore.DebugLevel))
65+
case "warn":
66+
zapLogger.Logger = zapLogger.WithOptions(zap.IncreaseLevel(zapcore.WarnLevel))
67+
case "error":
68+
zapLogger.Logger = zapLogger.WithOptions(zap.IncreaseLevel(zapcore.ErrorLevel))
69+
default:
70+
zapLogger.Logger = zapLogger.WithOptions(zap.IncreaseLevel(zapcore.InfoLevel))
71+
}
72+
73+
log = zapLogger // Update the logger for subsequent use
74+
}
75+
4676
// Determine the binary directory based on GOROOT or GOPATH/GOBIN settings.
4777
binDir, err := deps.FS.DetermineBinDir(config.Goroot)
4878
if err != nil {
49-
_ = log.Sync() // Ensure logs are flushed despite the error
79+
_ = log.Sync() // Flush logs; errors are ignored
5080

5181
return fmt.Errorf("failed to determine binary directory: %w", err)
5282
}
@@ -56,11 +86,16 @@ func Run(deps Dependencies, config Config) error {
5686
err = RunTUI(binDir, config, log, deps.FS, DefaultRunner{})
5787
} else {
5888
binaryPath := deps.FS.AdjustBinaryPath(binDir, config.Binary)
89+
5990
err = deps.FS.RemoveBinary(binaryPath, config.Binary, config.Verbose, log)
91+
if err == nil && !config.Verbose {
92+
// Print success message to stdout for non-verbose mode.
93+
fmt.Fprintf(os.Stdout, "Successfully removed %s\n", config.Binary)
94+
}
6095
}
6196

6297
if err != nil {
63-
_ = log.Sync() // Flush logs before returning the error
98+
_ = log.Sync() // Flush logs; errors are ignored
6499

65100
if config.Binary == "" {
66101
return fmt.Errorf("failed to run TUI: %w", err)
@@ -70,9 +105,7 @@ func Run(deps Dependencies, config Config) error {
70105
}
71106

72107
// Sync the logger to ensure all logs are written before exit.
73-
if err := log.Sync(); err != nil {
74-
return fmt.Errorf("failed to sync logger: %w", err)
75-
}
108+
_ = log.Sync() // Errors are ignored
76109

77110
return nil
78111
}

0 commit comments

Comments
 (0)