From 9e3b71ee7abb65f796d01c99a1421da3f79984a3 Mon Sep 17 00:00:00 2001 From: Justin Lambert <1409929+jlambert121@users.noreply.github.com> Date: Sun, 11 Aug 2024 10:27:24 -0600 Subject: [PATCH] switch logging to slog --- cli/boilerplate_cli.go | 4 ++ config/config.go | 12 +++--- config/config_test.go | 17 ++++++-- config/get_variables.go | 6 +-- config/get_variables_test.go | 7 ++- getter-helper/getter_helper.go | 6 +-- getter-helper/getter_helper_unix_test.go | 4 +- go.mod | 2 +- integration-tests/error_message_test.go | 1 + integration-tests/examples_test.go | 1 + integration-tests/required_version_test.go | 1 + integration-tests/slice_parsing_test.go | 1 + options/options.go | 5 +++ render/template_helpers.go | 5 ++- render/template_helpers_test.go | 8 +++- templates/engines_processor.go | 2 +- templates/skip_files_processor.go | 21 ++++----- templates/template_processor.go | 36 ++++++++++------ util/logger.go | 9 ---- util/prefixer/prefixer.go | 50 ++++++++++++++++++++++ util/shell.go | 9 +++- 21 files changed, 152 insertions(+), 55 deletions(-) delete mode 100644 util/logger.go create mode 100644 util/prefixer/prefixer.go diff --git a/cli/boilerplate_cli.go b/cli/boilerplate_cli.go index 5d520da7..be58c416 100644 --- a/cli/boilerplate_cli.go +++ b/cli/boilerplate_cli.go @@ -96,6 +96,10 @@ func CreateBoilerplateCli() *cli.App { Name: options.OptDisableDependencyPrompt, Usage: fmt.Sprintf("Do not prompt for confirmation to include dependencies. Has the same effect as --%s, without disabling variable prompts.", options.OptNonInteractive), }, + &cli.BoolFlag{ + Name: options.OptSilent, + Usage: "Do not output any log messages", + }, } // We pass JSON/YAML content to various CLI flags, such as --var, and this JSON/YAML content may contain commas or diff --git a/config/config.go b/config/config.go index b2d8b228..b857a32e 100644 --- a/config/config.go +++ b/config/config.go @@ -41,10 +41,10 @@ func (config *BoilerplateConfig) GetVariablesMap() map[string]variables.Variable // Implement the go-yaml unmarshal interface for BoilerplateConfig. We can't let go-yaml handle this itself because: // -// 1. Variable is an interface -// 2. We need to provide Defaults for optional fields, such as "type" -// 3. We want to validate the variable as part of the unmarshalling process so we never have invalid Variable or -// Dependency classes floating around +// 1. Variable is an interface +// 2. We need to provide Defaults for optional fields, such as "type" +// 3. We want to validate the variable as part of the unmarshalling process so we never have invalid Variable or +// Dependency classes floating around func (config *BoilerplateConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { var fields map[string]interface{} if err := unmarshal(&fields); err != nil { @@ -176,7 +176,7 @@ func LoadBoilerplateConfig(opts *options.BoilerplateOptions) (*BoilerplateConfig configPath := BoilerplateConfigPath(opts.TemplateFolder) if util.PathExists(configPath) { - util.Logger.Printf("Loading boilerplate config from %s", configPath) + opts.Logger.Info(fmt.Sprintf("Loading boilerplate config from %s", configPath)) bytes, err := ioutil.ReadFile(configPath) if err != nil { return nil, errors.WithStackTrace(err) @@ -184,7 +184,7 @@ func LoadBoilerplateConfig(opts *options.BoilerplateOptions) (*BoilerplateConfig return ParseBoilerplateConfig(bytes) } else if opts.OnMissingConfig == options.Ignore { - util.Logger.Printf("Warning: boilerplate config file not found at %s. The %s flag is set, so ignoring. Note that no variables will be available while generating.", configPath, options.OptMissingConfigAction) + opts.Logger.Info(fmt.Sprintf("Warning: boilerplate config file not found at %s. The %s flag is set, so ignoring. Note that no variables will be available while generating.", configPath, options.OptMissingConfigAction)) return &BoilerplateConfig{}, nil } else { // If the template URL is similar to a git URL, surface in error message that there may be a misspelling/typo. diff --git a/config/config_test.go b/config/config_test.go index 57d9e45e..862f65b3 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2,7 +2,9 @@ package config import ( "bytes" + "io" "io/ioutil" + "log/slog" "path" "path/filepath" "reflect" @@ -620,7 +622,10 @@ func TestParseBoilerplateConfigMultipleHooks(t *testing.T) { func TestLoadBoilerplateConfigFullConfig(t *testing.T) { t.Parallel() - actual, err := LoadBoilerplateConfig(&options.BoilerplateOptions{TemplateFolder: "../test-fixtures/config-test/full-config"}) + actual, err := LoadBoilerplateConfig(&options.BoilerplateOptions{ + Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), + TemplateFolder: "../test-fixtures/config-test/full-config", + }) expected := &BoilerplateConfig{ Partials: []string{"../templates/foo"}, Variables: []variables.Variable{ @@ -664,7 +669,10 @@ func TestLoadBoilerplateConfigNoConfigIgnore(t *testing.T) { t.Parallel() templateFolder := "../test-fixtures/config-test/no-config" - actual, err := LoadBoilerplateConfig(&options.BoilerplateOptions{TemplateFolder: templateFolder, OnMissingConfig: options.Ignore}) + actual, err := LoadBoilerplateConfig(&options.BoilerplateOptions{ + Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), + TemplateFolder: templateFolder, OnMissingConfig: options.Ignore, + }) expected := &BoilerplateConfig{} assert.Nil(t, err, "Unexpected error: %v", err) @@ -674,7 +682,10 @@ func TestLoadBoilerplateConfigNoConfigIgnore(t *testing.T) { func TestLoadBoilerplateConfigInvalidConfig(t *testing.T) { t.Parallel() - _, err := LoadBoilerplateConfig(&options.BoilerplateOptions{TemplateFolder: "../test-fixtures/config-test/invalid-config"}) + _, err := LoadBoilerplateConfig(&options.BoilerplateOptions{ + Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), + TemplateFolder: "../test-fixtures/config-test/invalid-config", + }) assert.NotNil(t, err) diff --git a/config/get_variables.go b/config/get_variables.go index 7b4617c3..7de1002a 100644 --- a/config/get_variables.go +++ b/config/get_variables.go @@ -170,10 +170,10 @@ func getVariable(variable variables.Variable, opts *options.BoilerplateOptions) valueFromVars, valueSpecifiedInVars := getVariableFromVars(variable, opts) if valueSpecifiedInVars { - util.Logger.Printf("Using value specified via command line options for variable '%s': %s", variable.FullName(), valueFromVars) + opts.Logger.Info(fmt.Sprintf("Using value specified via command line options for variable '%s': %s", variable.FullName(), valueFromVars)) return valueFromVars, nil } else if opts.NonInteractive && variable.Default() != nil { - util.Logger.Printf("Using default value for variable '%s': %v", variable.FullName(), variable.Default()) + opts.Logger.Info(fmt.Sprintf("Using default value for variable '%s': %v", variable.FullName(), variable.Default())) return variable.Default(), nil } else if opts.NonInteractive { return nil, errors.WithStackTrace(MissingVariableWithNonInteractiveMode(variable.FullName())) @@ -269,7 +269,7 @@ func getVariableFromUser(variable variables.Variable, opts *options.BoilerplateO if value == "" { // TODO: what if the user wanted an empty string instead of the default? - util.Logger.Printf("Using default value for variable '%s': %v", variable.FullName(), variable.Default()) + opts.Logger.Info(fmt.Sprintf("Using default value for variable '%s': %v", variable.FullName(), variable.Default())) return variable.Default(), nil } diff --git a/config/get_variables_test.go b/config/get_variables_test.go index 443fbeca..5ba347ec 100644 --- a/config/get_variables_test.go +++ b/config/get_variables_test.go @@ -1,6 +1,8 @@ package config import ( + "io" + "log/slog" "reflect" "testing" @@ -15,7 +17,7 @@ func TestGetVariableFromVarsEmptyVars(t *testing.T) { t.Parallel() variable := variables.NewStringVariable("foo") - opts := &options.BoilerplateOptions{} + opts := &options.BoilerplateOptions{Logger: slog.New(slog.NewJSONHandler(io.Discard, nil))} _, containsValue := getVariableFromVars(variable, opts) assert.False(t, containsValue) @@ -108,6 +110,7 @@ func TestGetVariableInVarsNonInteractive(t *testing.T) { variable := variables.NewStringVariable("foo") opts := &options.BoilerplateOptions{ + Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), NonInteractive: true, Vars: map[string]interface{}{ "key1": "value1", @@ -128,6 +131,7 @@ func TestGetVariableDefaultNonInteractive(t *testing.T) { variable := variables.NewStringVariable("foo").WithDefault("bar") opts := &options.BoilerplateOptions{ + Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), NonInteractive: true, Vars: map[string]interface{}{ "key1": "value1", @@ -225,6 +229,7 @@ func TestGetVariablesMatchFromVarsAndDefaults(t *testing.T) { t.Parallel() opts := &options.BoilerplateOptions{ + Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), NonInteractive: true, Vars: map[string]interface{}{ "key1": "value1", diff --git a/getter-helper/getter_helper.go b/getter-helper/getter_helper.go index a8c5ae2a..663520bb 100644 --- a/getter-helper/getter_helper.go +++ b/getter-helper/getter_helper.go @@ -3,6 +3,7 @@ package getter_helper import ( "context" "fmt" + "log/slog" "net/url" "os" "path/filepath" @@ -12,7 +13,6 @@ import ( urlhelper "github.com/hashicorp/go-getter/helper/url" "github.com/gruntwork-io/boilerplate/errors" - "github.com/gruntwork-io/boilerplate/util" ) var forcedRegexp = regexp.MustCompile(`^([A-Za-z0-9]+)::(.+)$`) @@ -100,7 +100,7 @@ func NewGetterClient(src string, dst string) (*getter.Client, error) { // DownloadTemplatesToTemporaryFolder uses the go-getter library to fetch the templates from the configured URL to a // temporary folder and returns the path to that folder. If there is a subdir in the template URL, return the combined // path as well. -func DownloadTemplatesToTemporaryFolder(templateUrl string) (string, string, error) { +func DownloadTemplatesToTemporaryFolder(templateUrl string, logger *slog.Logger) (string, string, error) { workingDir, err := getTempFolder() if err != nil { return workingDir, workingDir, errors.WithStackTrace(err) @@ -109,7 +109,7 @@ func DownloadTemplatesToTemporaryFolder(templateUrl string) (string, string, err // Always set a subdir path because go-getter can not clone into an existing dir. cloneDir := filepath.Join(workingDir, "wd") - util.Logger.Printf("Downloading templates from %s to %s", templateUrl, workingDir) + logger.Info(fmt.Sprintf("Downloading templates from %s to %s", templateUrl, workingDir)) // If there is a subdir component, we download everything and combine the path at the end to return the working path mainPath, subDir := getter.SourceDirSubdir(templateUrl) diff --git a/getter-helper/getter_helper_unix_test.go b/getter-helper/getter_helper_unix_test.go index c092a8d0..d6b199c8 100644 --- a/getter-helper/getter_helper_unix_test.go +++ b/getter-helper/getter_helper_unix_test.go @@ -5,6 +5,8 @@ package getter_helper import ( "fmt" + "io" + "log/slog" "os" "path/filepath" "testing" @@ -24,7 +26,7 @@ func TestDownloadTemplatesToTempDir(t *testing.T) { branch := git.GetCurrentBranchName(t) templateUrl := fmt.Sprintf("git@github.com:gruntwork-io/boilerplate.git//examples/for-learning-and-testing/variables?ref=%s", branch) - workingDir, workPath, err := DownloadTemplatesToTemporaryFolder(templateUrl) + workingDir, workPath, err := DownloadTemplatesToTemporaryFolder(templateUrl, slog.New(slog.NewJSONHandler(io.Discard, nil))) defer os.RemoveAll(workingDir) require.NoError(t, err, errors.PrintErrorWithStackTrace(err)) diff --git a/go.mod b/go.mod index 45e5bae1..146ce055 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gruntwork-io/boilerplate -go 1.18 +go 1.21 require ( github.com/AlecAivazis/survey/v2 v2.3.4 diff --git a/integration-tests/error_message_test.go b/integration-tests/error_message_test.go index 5f4ae303..1154cdd0 100644 --- a/integration-tests/error_message_test.go +++ b/integration-tests/error_message_test.go @@ -28,6 +28,7 @@ func TestMisspelledTemplateURLErrorMessage(t *testing.T) { "--output-folder", outputFolder, "--non-interactive", + "--silent", } runErr := app.Run(args) assert.Error(t, runErr, errors.PrintErrorWithStackTrace(runErr)) diff --git a/integration-tests/examples_test.go b/integration-tests/examples_test.go index a1599c96..3ee6e1bd 100644 --- a/integration-tests/examples_test.go +++ b/integration-tests/examples_test.go @@ -138,6 +138,7 @@ func testExample(t *testing.T, templateFolder string, outputFolder string, varFi "--non-interactive", "--missing-key-action", missingKeyAction, + "--silent", } // Special handling for the shell-disabled case, which we use to test that we can disable hooks and shell helpers diff --git a/integration-tests/required_version_test.go b/integration-tests/required_version_test.go index 2023cad6..ecf3002a 100644 --- a/integration-tests/required_version_test.go +++ b/integration-tests/required_version_test.go @@ -69,6 +69,7 @@ func runRequiredVersionExample(t *testing.T, templateFolder string) error { "--output-folder", outputPath, "--non-interactive", + "--silent", } return app.Run(args) } diff --git a/integration-tests/slice_parsing_test.go b/integration-tests/slice_parsing_test.go index ec92c62d..eae52284 100644 --- a/integration-tests/slice_parsing_test.go +++ b/integration-tests/slice_parsing_test.go @@ -34,6 +34,7 @@ func TestSliceParsing(t *testing.T) { "--var", fmt.Sprintf("MapValue=%s", mapValue), "--non-interactive", + "--silent", } runErr := app.Run(args) diff --git a/options/options.go b/options/options.go index 12564d66..520bfeba 100644 --- a/options/options.go +++ b/options/options.go @@ -2,6 +2,7 @@ package options import ( "fmt" + "log/slog" "github.com/urfave/cli/v2" @@ -20,6 +21,7 @@ const OptMissingConfigAction = "missing-config-action" const OptDisableHooks = "disable-hooks" const OptDisableShell = "disable-shell" const OptDisableDependencyPrompt = "disable-dependency-prompt" +const OptSilent = "silent" // The command-line options for the boilerplate app type BoilerplateOptions struct { @@ -36,6 +38,8 @@ type BoilerplateOptions struct { DisableHooks bool DisableShell bool DisableDependencyPrompt bool + Silent bool + Logger *slog.Logger } // Validate that the options have reasonable values and return an error if they don't @@ -96,6 +100,7 @@ func ParseOptions(cliContext *cli.Context) (*BoilerplateOptions, error) { DisableHooks: cliContext.Bool(OptDisableHooks), DisableShell: cliContext.Bool(OptDisableShell), DisableDependencyPrompt: cliContext.Bool(OptDisableDependencyPrompt), + Silent: cliContext.Bool(OptSilent), } if err := options.Validate(); err != nil { diff --git a/render/template_helpers.go b/render/template_helpers.go index 7d69bd66..cdefb4b5 100644 --- a/render/template_helpers.go +++ b/render/template_helpers.go @@ -255,7 +255,8 @@ func include(templatePath string, opts *options.BoilerplateOptions, path string, // Example: // // pathRelativeToTemplate("/foo/bar/template-file.txt, "../src/code.java") -// Returns: "/foo/src/code.java" +// +// Returns: "/foo/src/code.java" func PathRelativeToTemplate(templatePath string, filePath string) string { if path.IsAbs(filePath) { return filePath @@ -606,7 +607,7 @@ func keys(value interface{}) ([]string, error) { // string. func shell(templatePath string, opts *options.BoilerplateOptions, rawArgs ...string) (string, error) { if opts.DisableShell { - util.Logger.Printf("Shell helpers are disabled. Will not execute shell command '%v'. Returning placeholder value '%s' instead.", rawArgs, SHELL_DISABLED_PLACEHOLDER) + opts.Logger.Info(fmt.Sprintf("Shell helpers are disabled. Will not execute shell command '%v'. Returning placeholder value '%s' instead.", rawArgs, SHELL_DISABLED_PLACEHOLDER)) return SHELL_DISABLED_PLACEHOLDER, nil } diff --git a/render/template_helpers_test.go b/render/template_helpers_test.go index 86b87dbd..8ad73156 100644 --- a/render/template_helpers_test.go +++ b/render/template_helpers_test.go @@ -4,6 +4,8 @@ import ( "bufio" "bytes" "fmt" + "io" + "log/slog" "path/filepath" "reflect" "runtime" @@ -473,7 +475,11 @@ func TestShellError(t *testing.T) { func TestShellDisabled(t *testing.T) { t.Parallel() - output, err := shell(".", &options.BoilerplateOptions{NonInteractive: true, DisableShell: true}, "echo", "hi") + output, err := shell(".", &options.BoilerplateOptions{ + Logger: slog.New(slog.NewJSONHandler(io.Discard, nil)), + NonInteractive: true, + DisableShell: true, + }, "echo", "hi") assert.Nil(t, err, "Unexpected error: %v", err) assert.Equal(t, SHELL_DISABLED_PLACEHOLDER, output) } diff --git a/templates/engines_processor.go b/templates/engines_processor.go index 92521103..66fd223f 100644 --- a/templates/engines_processor.go +++ b/templates/engines_processor.go @@ -29,7 +29,7 @@ func processEngines( if err != nil { return nil, err } - debugLogForMatchedPaths(engine.Path, matchedPaths, "Engine", "Path") + debugLogForMatchedPaths(engine.Path, matchedPaths, "Engine", "Path", opts.Logger) processedEngine := ProcessedEngine{ EvaluatedPaths: matchedPaths, diff --git a/templates/skip_files_processor.go b/templates/skip_files_processor.go index 485b145e..bb4b48dd 100644 --- a/templates/skip_files_processor.go +++ b/templates/skip_files_processor.go @@ -1,6 +1,8 @@ package templates import ( + "fmt" + "log/slog" "path/filepath" zglob "github.com/mattn/go-zglob" @@ -8,7 +10,6 @@ import ( "github.com/gruntwork-io/boilerplate/errors" "github.com/gruntwork-io/boilerplate/options" "github.com/gruntwork-io/boilerplate/render" - "github.com/gruntwork-io/boilerplate/util" "github.com/gruntwork-io/boilerplate/variables" ) @@ -35,7 +36,7 @@ func processSkipFiles(skipFiles []variables.SkipFile, opts *options.BoilerplateO return nil, errors.WithStackTrace(err) } if skipFile.Path != "" { - debugLogForMatchedPaths(skipFile.Path, matchedPaths, "SkipFile", "Path") + debugLogForMatchedPaths(skipFile.Path, matchedPaths, "SkipFile", "Path", opts.Logger) } matchedNotPaths, err := renderGlobPath(opts, skipFile.NotPath, variables) @@ -43,7 +44,7 @@ func processSkipFiles(skipFiles []variables.SkipFile, opts *options.BoilerplateO return nil, errors.WithStackTrace(err) } if skipFile.NotPath != "" { - debugLogForMatchedPaths(skipFile.NotPath, matchedNotPaths, "SkipFile", "NotPath") + debugLogForMatchedPaths(skipFile.NotPath, matchedNotPaths, "SkipFile", "NotPath", opts.Logger) } renderedSkipIf, err := skipFileIfCondition(skipFile, opts, variables) @@ -75,20 +76,20 @@ func skipFileIfCondition(skipFile variables.SkipFile, opts *options.BoilerplateO // TODO: logger-debug - switch to debug if skipFile.Path != "" { - util.Logger.Printf("If attribute for SkipFile Path %s evaluated to '%s'", skipFile.Path, rendered) + opts.Logger.Info(fmt.Sprintf("If attribute for SkipFile Path %s evaluated to '%s'", skipFile.Path, rendered)) } else if skipFile.NotPath != "" { - util.Logger.Printf("If attribute for SkipFile NotPath %s evaluated to '%s'", skipFile.NotPath, rendered) + opts.Logger.Info(fmt.Sprintf("If attribute for SkipFile NotPath %s evaluated to '%s'", skipFile.NotPath, rendered)) } else { - util.Logger.Printf("WARN: SkipFile has no path or not_path!") + opts.Logger.Info(fmt.Sprintf("WARN: SkipFile has no path or not_path!")) } return rendered == "true", nil } -func debugLogForMatchedPaths(sourcePath string, paths []string, directiveName string, directiveAttribute string) { +func debugLogForMatchedPaths(sourcePath string, paths []string, directiveName string, directiveAttribute string, logger *slog.Logger) { // TODO: logger-debug - switch to debug - util.Logger.Printf("Following paths were picked up by %s attribute for %s (%s):", directiveAttribute, directiveName, sourcePath) + logger.Info(fmt.Sprintf("Following paths were picked up by %s attribute for %s (%s):", directiveAttribute, directiveName, sourcePath)) for _, path := range paths { - util.Logger.Printf("\t- %s", path) + logger.Info(fmt.Sprintf("\t- %s", path)) } } @@ -108,7 +109,7 @@ func renderGlobPath(opts *options.BoilerplateOptions, path string, variables map rawMatchedPaths, err := zglob.Glob(globPath) if err != nil { // TODO: logger-debug - switch to debug - util.Logger.Printf("ERROR: could not glob %s", globPath) + opts.Logger.Info(fmt.Sprintf("ERROR: could not glob %s", globPath)) return nil, errors.WithStackTrace(err) } // Canonicalize the matched paths prior to storage diff --git a/templates/template_processor.go b/templates/template_processor.go index e45dccb3..037066a9 100644 --- a/templates/template_processor.go +++ b/templates/template_processor.go @@ -2,6 +2,8 @@ package templates import ( "fmt" + "io" + "log/slog" "net/url" "os" "path" @@ -16,6 +18,7 @@ import ( "github.com/gruntwork-io/boilerplate/options" "github.com/gruntwork-io/boilerplate/render" "github.com/gruntwork-io/boilerplate/util" + "github.com/gruntwork-io/boilerplate/util/prefixer" "github.com/gruntwork-io/boilerplate/variables" ) @@ -28,12 +31,20 @@ const eachVarName = "__each__" // dependent boilerplate templates, and then execute this template. Note that we pass in rootOptions so that template // dependencies can inspect properties of the root template. func ProcessTemplate(options, rootOpts *options.BoilerplateOptions, thisDep variables.Dependency) error { + if options.Silent { + options.Logger = slog.New(slog.NewTextHandler(io.Discard, nil)) + rootOpts.Logger = options.Logger + } else if options.Logger == nil { + options.Logger = slog.New(prefixer.New()) + rootOpts.Logger = options.Logger + } + // If TemplateFolder is already set, use that directly as it is a local template. Otherwise, download to a temporary // working directory. if options.TemplateFolder == "" { - workingDir, templateFolder, err := getter_helper.DownloadTemplatesToTemporaryFolder(options.TemplateUrl) + workingDir, templateFolder, err := getter_helper.DownloadTemplatesToTemporaryFolder(options.TemplateUrl, options.Logger) defer func() { - util.Logger.Printf("Cleaning up working directory.") + options.Logger.Info(fmt.Sprintf("Cleaning up working directory.")) os.RemoveAll(workingDir) }() if err != nil { @@ -129,7 +140,7 @@ func processHook(hook variables.Hook, opts *options.BoilerplateOptions, vars map return err } if skip { - util.Logger.Printf("Skipping hook with command '%s'", hook.Command) + opts.Logger.Info(fmt.Sprintf("Skipping hook with command '%s'", hook.Command)) return nil } @@ -176,7 +187,7 @@ func processHook(hook variables.Hook, opts *options.BoilerplateOptions, vars map // Return true if the "skip" condition of this hook evaluates to true func shouldSkipHook(hook variables.Hook, opts *options.BoilerplateOptions, vars map[string]interface{}) (bool, error) { if opts.DisableHooks { - util.Logger.Printf("Hooks are disabled") + opts.Logger.Info(fmt.Sprintf("Hooks are disabled")) return true, nil } @@ -189,7 +200,7 @@ func shouldSkipHook(hook variables.Hook, opts *options.BoilerplateOptions, vars return false, err } - util.Logger.Printf("Skip attribute for hook with command '%s' evaluated to '%s'", hook.Command, rendered) + opts.Logger.Info(fmt.Sprintf("Skip attribute for hook with command '%s' evaluated to '%s'", hook.Command, rendered)) return rendered == "true", nil } @@ -229,7 +240,7 @@ func processDependency( return err } - util.Logger.Printf("Processing dependency %s, with template folder %s and output folder %s", dependency.Name, dependencyOptions.TemplateFolder, dependencyOptions.OutputFolder) + opts.Logger.Info(fmt.Sprintf("Processing dependency %s, with template folder %s and output folder %s", dependency.Name, dependencyOptions.TemplateFolder, dependencyOptions.OutputFolder)) return ProcessTemplate(dependencyOptions, opts, dependency) } @@ -256,7 +267,7 @@ func processDependency( return doProcess(originalVars) } } else { - util.Logger.Printf("Skipping dependency %s", dependency.Name) + opts.Logger.Info(fmt.Sprintf("Skipping dependency %s", dependency.Name)) return nil } } @@ -347,6 +358,7 @@ func cloneVariablesForDependency( DisableHooks: opts.DisableHooks, DisableShell: opts.DisableShell, DisableDependencyPrompt: opts.DisableDependencyPrompt, + Logger: opts.Logger, } // Start with the original variables. Note that it doesn't matter that originalVariables contains both CLI and @@ -444,7 +456,7 @@ func shouldSkipDependency(dependency variables.Dependency, opts *options.Boilerp return false, err } - util.Logger.Printf("Skip attribute for dependency %s evaluated to '%s'", dependency.Name, rendered) + opts.Logger.Info(fmt.Sprintf("Skip attribute for dependency %s evaluated to '%s'", dependency.Name, rendered)) return rendered == "true", nil } @@ -456,7 +468,7 @@ func processTemplateFolder( variables map[string]interface{}, partials []string, ) error { - util.Logger.Printf("Processing templates in %s and outputting generated files to %s", opts.TemplateFolder, opts.OutputFolder) + opts.Logger.Info(fmt.Sprintf("Processing templates in %s and outputting generated files to %s", opts.TemplateFolder, opts.OutputFolder)) // Process and render skip files and engines before walking so we only do the rendering operation once. processedSkipFiles, err := processSkipFiles(config.SkipFiles, opts, variables) @@ -471,7 +483,7 @@ func processTemplateFolder( return filepath.Walk(opts.TemplateFolder, func(path string, info os.FileInfo, err error) error { path = filepath.ToSlash(path) if shouldSkipPath(path, opts, processedSkipFiles) { - util.Logger.Printf("Skipping %s", path) + opts.Logger.Info(fmt.Sprintf("Skipping %s", path)) return nil } else if util.IsDir(path) { return createOutputDir(path, opts, variables) @@ -510,7 +522,7 @@ func createOutputDir(dir string, opts *options.BoilerplateOptions, variables map return err } - util.Logger.Printf("Creating folder %s", destination) + opts.Logger.Info(fmt.Sprintf("Creating folder %s", destination)) return os.MkdirAll(destination, 0777) } @@ -554,7 +566,7 @@ func copyFile(file string, opts *options.BoilerplateOptions, variables map[strin return err } - util.Logger.Printf("Copying %s to %s", file, destination) + opts.Logger.Info(fmt.Sprintf("Copying %s to %s", file, destination)) return util.CopyFile(file, destination) } diff --git a/util/logger.go b/util/logger.go deleted file mode 100644 index 88b54fe3..00000000 --- a/util/logger.go +++ /dev/null @@ -1,9 +0,0 @@ -package util - -import ( - "log" - "os" -) - -// A simple logger we can use to get consistent log formatting through out the app -var Logger = log.New(os.Stdout, "[boilerplate] ", log.LstdFlags) diff --git a/util/prefixer/prefixer.go b/util/prefixer/prefixer.go new file mode 100644 index 00000000..d8455f7e --- /dev/null +++ b/util/prefixer/prefixer.go @@ -0,0 +1,50 @@ +package prefixer + +import ( + "bytes" + "context" + "fmt" + "io" + "log/slog" + "os" + "sync" +) + +type Handler struct { + h slog.Handler + m *sync.Mutex + writer io.Writer +} + +func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool { + return h.h.Enabled(ctx, level) +} + +func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &Handler{h: h.h.WithAttrs(attrs), m: h.m, writer: h.writer} +} + +func (h *Handler) WithGroup(name string) slog.Handler { + return &Handler{h: h.h.WithGroup(name), m: h.m, writer: h.writer} +} + +func (h *Handler) Handle(ctx context.Context, r slog.Record) error { + msg := slog.StringValue(r.Message).String() + if msg != "" { + _, err := io.WriteString(h.writer, fmt.Sprintf("[boilerplate] %s\n", msg)) + return err + } + + return nil +} + +func New() *Handler { + buf := &bytes.Buffer{} + handler := &Handler{ + h: slog.NewTextHandler(buf, &slog.HandlerOptions{}), + m: &sync.Mutex{}, + writer: os.Stdout, + } + + return handler +} diff --git a/util/shell.go b/util/shell.go index b7423688..c0993697 100644 --- a/util/shell.go +++ b/util/shell.go @@ -1,16 +1,20 @@ package util import ( + "fmt" + "log/slog" "os" "os/exec" "strings" "github.com/gruntwork-io/boilerplate/errors" + "github.com/gruntwork-io/boilerplate/util/prefixer" ) // Run the given shell command with the given environment variables and arguments in the given working directory func RunShellCommandAndGetOutput(workingDir string, envVars []string, command string, args ...string) (string, error) { - Logger.Printf("Running command: %s %s", command, strings.Join(args, " ")) + logger := slog.New(prefixer.New()) + logger.Info(fmt.Sprintf("Running command: %s %s", command, strings.Join(args, " "))) cmd := exec.Command(command, args...) @@ -28,7 +32,8 @@ func RunShellCommandAndGetOutput(workingDir string, envVars []string, command st // Run the given shell command with the given environment variables and arguments in the given working directory func RunShellCommand(workingDir string, envVars []string, command string, args ...string) error { - Logger.Printf("Running command: %s %s", command, strings.Join(args, " ")) + logger := slog.New(prefixer.New()) + logger.Info(fmt.Sprintf("Running command: %s %s", command, strings.Join(args, " "))) cmd := exec.Command(command, args...)