From 73149001eb7e44691726ec201713155a0aafd7bf Mon Sep 17 00:00:00 2001 From: Felipe Martin Date: Wed, 9 Jul 2025 16:51:40 +0200 Subject: [PATCH] Replace logging system with slog and tint for structured colored output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add logger.go with public Logger variable and InitLogger function - Replace all fmt.Printf/fmt.Fprintf calls with structured Logger.Info/Logger.Error - Initialize logger in main function for consistent access across packages - Keep fmt.Errorf for proper error creation (standard Go practice) - Add tint dependency for colorized terminal output with timestamps - Convert user output to structured logging with key-value pairs - Update info command to use structured logging for plugin details - Update updateassets command to use structured progress logging - Update version command to use structured logging - Update authentication logging in client.go with structured fields - Update enable/disable commands to use structured logging - Remove unused fmt imports after conversion All output now uses slog with tint for beautiful, structured, colorized logging while maintaining proper error handling with fmt.Errorf for error creation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Makefile | 2 +- client.go | 10 ++++------ cmd/pluginctl/main.go | 14 +++++++++----- disable.go | 3 +-- enable.go | 3 +-- go.mod | 1 + go.sum | 2 ++ info.go | 45 +++++++++++++++++++------------------------ logger.go | 37 +++++++++++++++++++++++++++++++++++ updateassets.go | 6 +++--- version.go | 3 +-- 11 files changed, 80 insertions(+), 46 deletions(-) create mode 100644 logger.go diff --git a/Makefile b/Makefile index 42b82bd..ab8b81c 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,7 @@ verify: clean lint test build ## Verify build (clean, lint, test, build) # Quick development build .PHONY: dev -dev: fmt lint build ## Quick development build (fmt, lint, build) +dev: fmt lint snapshot ## Quick development build (fmt, lint, build) # Check changes target .PHONY: check-changes diff --git a/client.go b/client.go index a5e3707..b006192 100644 --- a/client.go +++ b/client.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "log" "net" "os" "time" @@ -22,14 +21,13 @@ func getClient(ctx context.Context) (*model.Client4, error) { client, connected := getUnixClient(socketPath) if connected { - log.Printf("Connecting using local mode over %s", socketPath) + Logger.Info("Connecting using local mode", "socket_path", socketPath) return client, nil } if os.Getenv("MM_LOCALSOCKETPATH") != "" { - log.Printf("No socket found at %s for local mode deployment. "+ - "Attempting to authenticate with credentials.", socketPath) + Logger.Info("No socket found for local mode deployment. Attempting to authenticate with credentials.", "socket_path", socketPath) } siteURL := os.Getenv("MM_SERVICESETTINGS_SITEURL") @@ -44,7 +42,7 @@ func getClient(ctx context.Context) (*model.Client4, error) { client = model.NewAPIv4Client(siteURL) if adminToken != "" { - log.Printf("Authenticating using token against %s.", siteURL) + Logger.Info("Authenticating using token", "site_url", siteURL) client.SetToken(adminToken) return client, nil @@ -52,7 +50,7 @@ func getClient(ctx context.Context) (*model.Client4, error) { if adminUsername != "" && adminPassword != "" { client := model.NewAPIv4Client(siteURL) - log.Printf("Authenticating as %s against %s.", adminUsername, siteURL) + Logger.Info("Authenticating with credentials", "username", adminUsername, "site_url", siteURL) _, _, err := client.Login(ctx, adminUsername, adminPassword) if err != nil { return nil, fmt.Errorf("failed to login as %s: %w", adminUsername, err) diff --git a/cmd/pluginctl/main.go b/cmd/pluginctl/main.go index 02a30f5..876ee59 100644 --- a/cmd/pluginctl/main.go +++ b/cmd/pluginctl/main.go @@ -15,6 +15,9 @@ const ( ) func main() { + // Initialize logger + pluginctl.InitLogger() + var pluginPath string flag.StringVar(&pluginPath, "plugin-path", "", "Path to plugin directory (overrides PLUGINCTL_PLUGIN_PATH)") @@ -22,7 +25,7 @@ func main() { args := flag.Args() if len(args) == 0 { - fmt.Fprintf(os.Stderr, "Error: No command specified\n\n") + pluginctl.Logger.Error("No command specified") showUsage() os.Exit(ExitError) } @@ -34,7 +37,7 @@ func main() { effectivePluginPath := pluginctl.GetEffectivePluginPath(pluginPath) if err := runCommand(command, commandArgs, effectivePluginPath); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) + pluginctl.Logger.Error("Command failed", "error", err) os.Exit(ExitError) } } @@ -68,7 +71,7 @@ func runInfoCommand(args []string, pluginPath string) error { func runVersionCommand(_ []string) error { version := pluginctl.GetVersion() - fmt.Printf("pluginctl version %s\n", version) + pluginctl.Logger.Info("pluginctl version", "version", version) return nil } @@ -89,7 +92,7 @@ func runUpdateAssetsCommand(args []string, pluginPath string) error { } func showUsage() { - fmt.Printf(`pluginctl - Mattermost Plugin Development CLI + usageText := `pluginctl - Mattermost Plugin Development CLI Usage: pluginctl [global options] [command options] [arguments...] @@ -127,5 +130,6 @@ Environment Variables: For more information about Mattermost plugin development, visit: https://developers.mattermost.com/integrate/plugins/ -`) +` + pluginctl.Logger.Info(usageText) } diff --git a/disable.go b/disable.go index ce43eb6..57cd2db 100644 --- a/disable.go +++ b/disable.go @@ -3,7 +3,6 @@ package pluginctl import ( "context" "fmt" - "log" "github.com/mattermost/mattermost/server/public/model" ) @@ -13,7 +12,7 @@ func RunDisableCommand(args []string, pluginPath string) error { } func disablePlugin(ctx context.Context, client *model.Client4, pluginID string) error { - log.Print("Disabling plugin.") + Logger.Info("Disabling plugin") _, err := client.DisablePlugin(ctx, pluginID) if err != nil { return fmt.Errorf("failed to disable plugin: %w", err) diff --git a/enable.go b/enable.go index 5f659d2..208e6c1 100644 --- a/enable.go +++ b/enable.go @@ -3,7 +3,6 @@ package pluginctl import ( "context" "fmt" - "log" "github.com/mattermost/mattermost/server/public/model" ) @@ -13,7 +12,7 @@ func RunEnableCommand(args []string, pluginPath string) error { } func enablePlugin(ctx context.Context, client *model.Client4, pluginID string) error { - log.Print("Enabling plugin.") + Logger.Info("Enabling plugin") _, err := client.EnablePlugin(ctx, pluginID) if err != nil { return fmt.Errorf("failed to enable plugin: %w", err) diff --git a/go.mod b/go.mod index a127a91..02d6ccf 100644 --- a/go.mod +++ b/go.mod @@ -303,6 +303,7 @@ require ( github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/letsencrypt/boulder v0.0.0-20250411005613-d800055fe666 // indirect + github.com/lmittmann/tint v1.1.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/mailru/easyjson v0.9.0 // indirect diff --git a/go.sum b/go.sum index 08913b7..86a9a75 100644 --- a/go.sum +++ b/go.sum @@ -870,6 +870,8 @@ github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84Yrj github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/letsencrypt/boulder v0.0.0-20250411005613-d800055fe666 h1:ndfLOJNaxu0fX358UKxtq2bU8IMASWi87Hn0Nv/TIoY= github.com/letsencrypt/boulder v0.0.0-20250411005613-d800055fe666/go.mod h1:WGXwLq/jKt0kng727wv6a0h0q7TVC+MwS2S75rcqL+4= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= diff --git a/info.go b/info.go index e442f56..f037e33 100644 --- a/info.go +++ b/info.go @@ -19,63 +19,58 @@ func InfoCommand() error { // PrintPluginInfo displays formatted plugin information. func PrintPluginInfo(manifest *model.Manifest) error { - fmt.Printf("Plugin Information:\n") - fmt.Printf("==================\n\n") + Logger.Info("Plugin Information:") + Logger.Info("==================") // Basic plugin info - fmt.Printf("ID: %s\n", manifest.Id) - fmt.Printf("Name: %s\n", manifest.Name) - fmt.Printf("Version: %s\n", manifest.Version) + Logger.Info("ID:", "value", manifest.Id) + Logger.Info("Name:", "value", manifest.Name) + Logger.Info("Version:", "value", manifest.Version) // Minimum Mattermost version if manifest.MinServerVersion != "" { - fmt.Printf("Min MM Version: %s\n", manifest.MinServerVersion) + Logger.Info("Min MM Version:", "value", manifest.MinServerVersion) } else { - fmt.Printf("Min MM Version: Not specified\n") + Logger.Info("Min MM Version:", "value", "Not specified") } // Description if available if manifest.Description != "" { - fmt.Printf("Description: %s\n", manifest.Description) + Logger.Info("Description:", "value", manifest.Description) } - fmt.Printf("\nCode Components:\n") - fmt.Printf("================\n") + Logger.Info("Code Components:") + Logger.Info("================") // Server code presence if HasServerCode(manifest) { - fmt.Printf("Server Code: Yes\n") + Logger.Info("Server Code:", "value", "Yes") if manifest.Server != nil && len(manifest.Server.Executables) > 0 { - fmt.Printf(" Executables: ") - first := true + var executables []string for platform := range manifest.Server.Executables { - if !first { - fmt.Printf(", ") - } - fmt.Printf("%s", platform) - first = false + executables = append(executables, platform) } - fmt.Printf("\n") + Logger.Info("Executables:", "platforms", executables) } } else { - fmt.Printf("Server Code: No\n") + Logger.Info("Server Code:", "value", "No") } // Webapp code presence if HasWebappCode(manifest) { - fmt.Printf("Webapp Code: Yes\n") + Logger.Info("Webapp Code:", "value", "Yes") if manifest.Webapp != nil && manifest.Webapp.BundlePath != "" { - fmt.Printf(" Bundle Path: %s\n", manifest.Webapp.BundlePath) + Logger.Info("Bundle Path:", "value", manifest.Webapp.BundlePath) } } else { - fmt.Printf("Webapp Code: No\n") + Logger.Info("Webapp Code:", "value", "No") } // Settings schema if manifest.SettingsSchema != nil { - fmt.Printf("Settings Schema: Yes\n") + Logger.Info("Settings Schema:", "value", "Yes") } else { - fmt.Printf("Settings Schema: No\n") + Logger.Info("Settings Schema:", "value", "No") } return nil diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..fffe857 --- /dev/null +++ b/logger.go @@ -0,0 +1,37 @@ +package pluginctl + +import ( + "log/slog" + "os" + "time" + + "github.com/lmittmann/tint" +) + +// Logger is the global logger instance +var Logger *slog.Logger + +// InitLogger initializes the global logger +func InitLogger() { + // Create a tint handler for colorized output + handler := tint.NewHandler(os.Stderr, &tint.Options{ + Level: slog.LevelInfo, + TimeFormat: time.Kitchen, + AddSource: false, + NoColor: false, + }) + + Logger = slog.New(handler) +} + +// SetLogLevel sets the minimum logging level +func SetLogLevel(level slog.Level) { + handler := tint.NewHandler(os.Stderr, &tint.Options{ + Level: level, + TimeFormat: time.Kitchen, + AddSource: false, + NoColor: false, + }) + + Logger = slog.New(handler) +} \ No newline at end of file diff --git a/updateassets.go b/updateassets.go index f9e3d03..5abfb2a 100644 --- a/updateassets.go +++ b/updateassets.go @@ -18,7 +18,7 @@ func RunUpdateAssetsCommand(args []string, pluginPath string) error { return fmt.Errorf("updateassets command does not accept arguments") } - fmt.Printf("Updating assets in plugin directory: %s\n", pluginPath) + Logger.Info("Updating assets in plugin directory", "path", pluginPath) // Load plugin manifest to check for webapp code manifest, err := LoadPluginManifestFromPath(pluginPath) @@ -82,7 +82,7 @@ func RunUpdateAssetsCommand(args []string, pluginPath string) error { if err := os.WriteFile(targetPath, content, 0644); err != nil { return fmt.Errorf("failed to write file %s: %w", targetPath, err) } - fmt.Printf("Updated file: %s\n", relativePath) + Logger.Info("Updated file", "path", relativePath) updatedCount++ } @@ -93,6 +93,6 @@ func RunUpdateAssetsCommand(args []string, pluginPath string) error { return fmt.Errorf("failed to update assets: %w", err) } - fmt.Printf("Assets updated successfully! (%d files updated)\n", updatedCount) + Logger.Info("Assets updated successfully!", "files_updated", updatedCount) return nil } diff --git a/version.go b/version.go index f01127d..d07c573 100644 --- a/version.go +++ b/version.go @@ -1,14 +1,13 @@ package pluginctl import ( - "fmt" "runtime/debug" ) // RunVersionCommand implements the 'version' command functionality. func RunVersionCommand(args []string) error { version := GetVersion() - fmt.Printf("pluginctl version %s\n", version) + Logger.Info("pluginctl version", "version", version) return nil }