Repository: cloudposse/atmos
Pull request #1103: Remove unused flag code in root
Author: login: samtholiya
Labels: name: patch
description: A minor, backward compatible change
Created: 2025-03-01T13:07:15Z | Merged: 2025-03-17T21:40:37Z

## what

* There is no use of this code here because we set the values already [here](https://github.com/cloudposse/atmos/blob/main/cmd/root.go#L187-L199)

## why

* We were suspecting this code to not work anyway and cleaning up the code is much better, it avoids confusion. Especially when we already set the values [here](https://github.com/cloudposse/atmos/blob/main/cmd/root.go#L187-L199)

> Note: We are not refactoring the whole logging logic here. This is just a clean up that would help us reduce the pr size. Also reducing the places where we repeat setting the values

## references
* https://linear.app/cloudposse/issue/DEV-3074/remove-unused-flags-value-code

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a unified logging helper that provides consistent terminal messaging.
  - Enhanced logging configuration capabilities to allow dynamic adjustments based on environment variables and command-line inputs.

- **Refactor**
  - Centralized the logging configuration to pull settings directly from the configuration, improving error handling and message consistency.
  - Added new functions for parsing command-line flags and setting log configuration.

- **Tests**
  - Updated CLI log outputs and error messages to reflect revised severity labels and environment details.
  - Adjusted expected outputs in test cases to align with new logging behavior.

- **Chores**
  - Removed deprecated environment variable support for overriding logging settings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/cmd/root.go b/cmd/root.go
index e64f7c8697..65d7baf122 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -17,6 +17,7 @@ import (
 	"github.com/cloudposse/atmos/internal/tui/templates"
 	tuiUtils "github.com/cloudposse/atmos/internal/tui/utils"
 	cfg "github.com/cloudposse/atmos/pkg/config"
+	"github.com/cloudposse/atmos/pkg/logger"
 	"github.com/cloudposse/atmos/pkg/schema"
 	"github.com/cloudposse/atmos/pkg/utils"
 	u "github.com/cloudposse/atmos/pkg/utils"
@@ -47,16 +48,6 @@ var RootCmd = &cobra.Command{
 			cmd.SilenceErrors = true
 		}
 		configAndStacksInfo := schema.ConfigAndStacksInfo{}
-		// TODO: Check if these value being set was actually required
-		if cmd.Flags().Changed("logs-level") {
-			logsLevel, _ := cmd.Flags().GetString("logs-level")
-			configAndStacksInfo.LogsLevel = logsLevel
-		}
-		if cmd.Flags().Changed("logs-file") {
-			logsFile, _ := cmd.Flags().GetString("logs-file")
-			configAndStacksInfo.LogsFile = logsFile
-		}
-
 		// Only validate the config, don't store it yet since commands may need to add more info
 		_, err := cfg.InitCliConfig(configAndStacksInfo, false)
 		if err != nil {
@@ -123,42 +114,11 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
-}
-
-// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
-// as Cobra typically handles this.
-
-// If there's no alternative, this approach may be necessary.
-// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
-
-// Function to manually parse flags with double dash "--" like Cobra
-func parseFlags(args []string) (map[string]string, error) {
-	flags := make(map[string]string)
-	for i := 0; i < len(args); i++ {
-		arg := args[i]
-
-		// Check if the argument starts with '--' (double dash)
-		if strings.HasPrefix(arg, "--") {
-			// Strip the '--' prefix and check if it's followed by a value
-			arg = arg[2:]
-			if strings.Contains(arg, "=") {
-				// Case like --flag=value
-				parts := strings.SplitN(arg, "=", 2)
-				flags[parts[0]] = parts[1]
-			} else if i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") {
-				// Case like --flag value
-				flags[arg] = args[i+1]
-				i++ // Skip the next argument as it's the value
-			} else {
-				// Case where flag has no value, e.g., --flag (we set it to "true")
-				flags[arg] = "true"
-			}
-		} else {
-			// It's a regular argument, not a flag, so we skip
-			continue
-		}
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all // The reason to escape this is because it is expected to fail fast. The reason for ignoring all is because we also get a lint error to execute defer logFile.Close() before this but that is not required
+		log.Fatal(err)
 	}
-	return flags, nil
+	log.Debug("Set", "logs-level", log.GetLevel(), "logs-file", atmosConfig.Logs.File)
 }
 
 // Execute adds all child commands to the root command and sets flags appropriately.
@@ -178,21 +138,6 @@ func Execute() error {
 		}
 	}
 
-	// TODO: This is a quick patch to mitigate the issue we can look for better code later
-	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
-		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
-	}
-	flagKeyValue, _ := parseFlags(os.Args)
-	if v, ok := flagKeyValue["logs-level"]; ok {
-		atmosConfig.Logs.Level = v
-	}
-	if os.Getenv("ATMOS_LOGS_FILE") != "" {
-		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
-	}
-	if v, ok := flagKeyValue["logs-file"]; ok {
-		atmosConfig.Logs.File = v
-	}
-
 	// Set the log level for the charmbracelet/log package based on the atmosConfig
 	setupLogger(&atmosConfig)
 
diff --git a/cmd/terraform_generate_backend_test.go b/cmd/terraform_generate_backend_test.go
index 9b9320a8e1..5f1f80efee 100644
--- a/cmd/terraform_generate_backend_test.go
+++ b/cmd/terraform_generate_backend_test.go
@@ -28,7 +28,7 @@ func TestTerraformGenerateBackendCmd(t *testing.T) {
 
 	// Execute the command
 	RootCmd.SetArgs([]string{"terraform", "generate", "backend", "component-1", "-s", "nonprod"})
-	err = RootCmd.Execute()
+	err = Execute()
 	assert.NoError(t, err, "'TestTerraformGenerateBackendCmd' should execute without error")
 
 	// Close the writer and restore stdout
diff --git a/internal/exec/terraform_outputs.go b/internal/exec/terraform_outputs.go
index 6385eb8261..346a5c4378 100644
--- a/internal/exec/terraform_outputs.go
+++ b/internal/exec/terraform_outputs.go
@@ -296,9 +296,7 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		u.PrintfMessageToTUI("\r✗ %s\n", message)
 		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
 	}
 
@@ -306,9 +304,7 @@ func GetTerraformOutput(
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		u.PrintfMessageToTUI("\r✗ %s\n", message)
 		l.Fatal("Failed to get remote state backend static type outputs", "error", err)
 	}
 
@@ -321,9 +317,7 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
+			u.PrintfMessageToTUI("\r✗ %s\n", message)
 			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
 		}
 
@@ -331,11 +325,7 @@ func GetTerraformOutput(
 		terraformOutputsCache.Store(stackSlug, terraformOutputs)
 		result = getTerraformOutputVariable(atmosConfig, component, stack, terraformOutputs, output)
 	}
-
-	if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-		// Show success
-		fmt.Printf("\r✓ %s\n", message)
-	}
+	u.PrintfMessageToTUI("\r✓ %s\n", message)
 
 	return result
 }
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 830a92873d..7c2d6411b3 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -7,6 +7,7 @@ import (
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 
 	"github.com/cloudposse/atmos/pkg/config/go-homedir"
 
@@ -386,11 +387,65 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	// Issue: https://linear.app/cloudposse/issue/DEV-3093/create-a-cli-command-core-library
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra.
+func parseFlags() map[string]string {
+	args := os.Args
+	flags := make(map[string]string)
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+		// Check if the argument starts with '--' (double dash)
+		if !strings.HasPrefix(arg, "--") {
+			continue
+		}
+		// Strip the '--' prefix and check if it's followed by a value
+		arg = arg[2:]
+		switch {
+		case strings.Contains(arg, "="):
+			// Case like --flag=value
+			parts := strings.SplitN(arg, "=", 2)
+			flags[parts[0]] = parts[1]
+		case i+1 < len(args) && !strings.HasPrefix(args[i+1], "--"):
+			// Case like --flag value
+			flags[arg] = args[i+1]
+			i++ // Skip the next argument as it's the value
+		default:
+			// Case where flag has no value, e.g., --flag (we set it to "true")
+			flags[arg] = "true"
+		}
+	}
+	return flags
+}
+
 // https://github.com/NCAR/go-figure
 // https://github.com/spf13/viper/issues/181
 // https://medium.com/@bnprashanth256/reading-configuration-files-and-environment-variables-in-go-golang-c2607f912b63
diff --git a/pkg/config/utils.go b/pkg/config/utils.go
index 67c7a9173c..1063a52ec3 100644
--- a/pkg/config/utils.go
+++ b/pkg/config/utils.go
@@ -356,26 +356,9 @@ func processEnvVars(atmosConfig *schema.AtmosConfiguration) error {
 		}
 	}
 
-	logsFile := os.Getenv("ATMOS_LOGS_FILE")
-	if len(logsFile) > 0 {
-		u.LogDebug(fmt.Sprintf("Found ENV var ATMOS_LOGS_FILE=%s", logsFile))
-		atmosConfig.Logs.File = logsFile
-	}
-
-	logsLevel := os.Getenv("ATMOS_LOGS_LEVEL")
-	if len(logsLevel) > 0 {
-		u.LogDebug(fmt.Sprintf("Found ENV var ATMOS_LOGS_LEVEL=%s", logsLevel))
-		// Validate the log level before setting it
-		if _, err := logger.ParseLogLevel(logsLevel); err != nil {
-			return err
-		}
-		// Only set the log level if validation passes
-		atmosConfig.Logs.Level = logsLevel
-	}
-
 	tfAppendUserAgent := os.Getenv("ATMOS_COMPONENTS_TERRAFORM_APPEND_USER_AGENT")
 	if len(tfAppendUserAgent) > 0 {
-		u.LogDebug(fmt.Sprintf("Found ENV var ATMOS_COMPONENTS_TERRAFORM_APPEND_USER_AGENT=%s", tfAppendUserAgent))
+		log.Debug("Found ENV", "ATMOS_COMPONENTS_TERRAFORM_APPEND_USER_AGENT", tfAppendUserAgent)
 		atmosConfig.Components.Terraform.AppendUserAgent = tfAppendUserAgent
 	}
 
diff --git a/pkg/utils/log_utils.go b/pkg/utils/log_utils.go
index 767cc2b59a..a6ff97c486 100644
--- a/pkg/utils/log_utils.go
+++ b/pkg/utils/log_utils.go
@@ -35,8 +35,13 @@ func PrintErrorInColor(message string) {
 	_, _ = messageColor.Fprint(os.Stderr, message)
 }
 
+// PrintfMessageToTUI prints the message to the stderr.
+func PrintfMessageToTUI(message string, args ...any) {
+	fmt.Fprintf(os.Stderr, message, args...)
+}
+
 // Deprecated: Use `log.Fatal` instead. This function will be removed in a future release.
-// LogErrorAndExit logs errors to std.Error and exits with an error code
+// LogErrorAndExit logs errors to std.Error and exits with an error code.
 func LogErrorAndExit(err error) {
 	log.Error(err)
 
diff --git a/tests/snapshots/TestCLICommands_Invalid_Log_Level_in_Environment_Variable.stderr.golden b/tests/snapshots/TestCLICommands_Invalid_Log_Level_in_Environment_Variable.stderr.golden
index 061b85a7e6..f0372744eb 100644
--- a/tests/snapshots/TestCLICommands_Invalid_Log_Level_in_Environment_Variable.stderr.golden
+++ b/tests/snapshots/TestCLICommands_Invalid_Log_Level_in_Environment_Variable.stderr.golden
@@ -1 +1 @@
-ERRO Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]
+FATA Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]
diff --git a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Command_Line_Flag.stdout.golden b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Command_Line_Flag.stdout.golden
index 82c26c3598..c39a129255 100644
--- a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Command_Line_Flag.stdout.golden
+++ b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Command_Line_Flag.stdout.golden
@@ -1,3 +1,3 @@
 
-👽 Atmos test on darwin/arm64
+👽 Atmos test on linux/amd64
 
diff --git a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stderr.golden b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stderr.golden
index a75ad0264a..c65da0fe06 100644
--- a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stderr.golden
+++ b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stderr.golden
@@ -1,2 +1,3 @@
+DEBU Set logs-level=debug logs-file=/dev/stderr
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
diff --git a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stdout.golden b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stdout.golden
index 82c26c3598..c39a129255 100644
--- a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stdout.golden
+++ b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Config_File.stdout.golden
@@ -1,3 +1,3 @@
 
-👽 Atmos test on darwin/arm64
+👽 Atmos test on linux/amd64
 
diff --git a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stderr.golden b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stderr.golden
index 7b40edf9e4..c65da0fe06 100644
--- a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stderr.golden
+++ b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stderr.golden
@@ -1,3 +1,3 @@
-DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug
+DEBU Set logs-level=debug logs-file=/dev/stderr
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
diff --git a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stdout.golden b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stdout.golden
index 82c26c3598..c39a129255 100644
--- a/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stdout.golden
+++ b/tests/snapshots/TestCLICommands_Valid_Log_Level_in_Environment_Variable.stdout.golden
@@ -1,3 +1,3 @@
 
-👽 Atmos test on darwin/arm64
+👽 Atmos test on linux/amd64
 
diff --git a/tests/snapshots/TestCLICommands_Valid_log_file_in_env_should_be_priortized_over_config.stdout.golden b/tests/snapshots/TestCLICommands_Valid_log_file_in_env_should_be_priortized_over_config.stdout.golden
index ef4092739d..b52b9d9f0c 100644
--- a/tests/snapshots/TestCLICommands_Valid_log_file_in_env_should_be_priortized_over_config.stdout.golden
+++ b/tests/snapshots/TestCLICommands_Valid_log_file_in_env_should_be_priortized_over_config.stdout.golden
@@ -1,6 +1,5 @@
-DEBU Found ENV var ATMOS_LOGS_FILE=/dev/stdout
+DEBU Set logs-level=debug logs-file=/dev/stdout
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
-DEBU Set using command line argument --logs-level=Debug
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
 
 👽 Atmos test on darwin/arm64
diff --git a/tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden b/tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
index 4c8f64a347..b52b9d9f0c 100644
--- a/tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
+++ b/tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
@@ -1,7 +1,5 @@
-DEBU Found ENV var ATMOS_LOGS_FILE=/dev/stderr
+DEBU Set logs-level=debug logs-file=/dev/stdout
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
-DEBU Set using command line argument --logs-level=Debug
-DEBU Set using command line argument --logs-file=/dev/stdout
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
 
 👽 Atmos test on darwin/arm64
diff --git a/tests/snapshots/TestCLICommands_Valid_log_level_in_env_should_be_priortized_over_config.stderr.golden b/tests/snapshots/TestCLICommands_Valid_log_level_in_env_should_be_priortized_over_config.stderr.golden
index 7b40edf9e4..c65da0fe06 100644
--- a/tests/snapshots/TestCLICommands_Valid_log_level_in_env_should_be_priortized_over_config.stderr.golden
+++ b/tests/snapshots/TestCLICommands_Valid_log_level_in_env_should_be_priortized_over_config.stderr.golden
@@ -1,3 +1,3 @@
-DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug
+DEBU Set logs-level=debug logs-file=/dev/stderr
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
diff --git a/tests/snapshots/TestCLICommands_Valid_log_level_in_flag_should_be_priortized_over_env_and_config.stderr.golden b/tests/snapshots/TestCLICommands_Valid_log_level_in_flag_should_be_priortized_over_env_and_config.stderr.golden
index e71bbc1818..c65da0fe06 100644
--- a/tests/snapshots/TestCLICommands_Valid_log_level_in_flag_should_be_priortized_over_env_and_config.stderr.golden
+++ b/tests/snapshots/TestCLICommands_Valid_log_level_in_flag_should_be_priortized_over_env_and_config.stderr.golden
@@ -1,4 +1,3 @@
-DEBU Found ENV var ATMOS_LOGS_LEVEL=Info
+DEBU Set logs-level=debug logs-file=/dev/stderr
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
-DEBU Set using command line argument --logs-level=Debug
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
diff --git a/tests/test-cases/atmos-functions.yaml b/tests/test-cases/atmos-functions.yaml
index 1f3a2356f0..fbd609c69a 100644
--- a/tests/test-cases/atmos-functions.yaml
+++ b/tests/test-cases/atmos-functions.yaml
@@ -18,12 +18,11 @@ tests:
       os: !not windows
     expect:
       exit_code: 0
+      # This will output in stdout because of the way our tty simulator works.
       stdout:
         - "Fetching baz output from component-3 in nonprod"
         - "Fetching foo output from component-1 in nonprod"
         - "Fetching bar output from component-2 in nonprod"
-      stderr:
-        - "^$"
 
   - name: terraform output function (no tty)
     enabled: true
@@ -41,7 +40,7 @@ tests:
       ATMOS_LOGS_LEVEL: Debug
     expect:
       exit_code: 0
-      stdout:
+      stderr:
         - "Fetching baz output from component-3 in nonprod"
         - "Fetching foo output from component-1 in nonprod"
         - "Fetching bar output from component-2 in nonprod"
diff --git a/tests/test-cases/log-level-validation.yaml b/tests/test-cases/log-level-validation.yaml
index 52feedfcb1..dc3a4ecbc3 100644
--- a/tests/test-cases/log-level-validation.yaml
+++ b/tests/test-cases/log-level-validation.yaml
@@ -74,7 +74,7 @@ tests:
       stdout:
         - '^\n👽 Atmos (\d+\.\d+\.\d+|test) on [a-z]+/[a-z0-9_]+\n\n'
       stderr:
-        - "DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug"
+        - "Set logs-level=debug logs-file=/dev/stderr"
       exit_code: 0
 
   - name: "Valid Log Level in Command Line Flag"


Reviews:
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T15:49:44Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T15:51:57Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-01T15:53:08Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T16:02:31Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-02T13:28:01Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T13:28:37Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T13:33:37Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T21:07:20Z
user: login: osterman
body: We need to distinguish between log output, which is for developers and console output, which is for users. Previously, the code was not clear about this and muddled it by using log levels to control console output. Hence it makes sense why you are condensing it into log statements, but the original logic was flawed. 

Please introduce a function so we make it clear and deliberate when we are showing console output as opposed to log output .

For example, in the future, we might introduce a quiet flag, which affects the console output, but does not affect the log output.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T14:11:13Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T21:21:01Z
user: login: coderabbitai[bot]
body: > [!CAUTION]
> Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.  If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

**Actionable comments posted: 1**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>cmd/root.go (1)</summary><blockquote>

`177-190`: _⚠️ Potential issue_

**Remove unnecessary flag code as mentioned in PR objectives.**

According to the PR objectives, this code block is redundant since these values are already set elsewhere. The code is manipulating environment variables and flags for log settings that are now handled through the atmosConfig structure.


This code block should be removed since it's the primary target of this PR. The purpose stated in the PR is to "Remove unused flag code in root" and this appears to be the exact code in question.

```diff
-	// TODO: This is a quick patch to mitigate the issue we can look for better code later
-	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
-		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
-	}
-	flagKeyValue, _ := parseFlags(os.Args)
-	if v, ok := flagKeyValue["logs-level"]; ok {
-		atmosConfig.Logs.Level = v
-	}
-	if os.Getenv("ATMOS_LOGS_FILE") != "" {
-		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
-	}
-	if v, ok := flagKeyValue["logs-file"]; ok {
-		atmosConfig.Logs.File = v
-	}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🛑 Comments failed to post (1)</summary><blockquote>

<details>
<summary>cmd/root.go (1)</summary><blockquote>

117-121: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Good logging enhancement, but check the static analysis warning.**

The addition of log level validation is a useful safety measure to ensure configuration validity. However, there's a potential issue with the error handling.


The static analysis tool has flagged line 119 with an "exitAfterDefer" warning. This indicates that if `log.Fatal` is called, the deferred `logFile.Close()` on line 112 would never execute, potentially causing a resource leak.

---

🏁 Script executed:

```shell
#!/bin/bash
# Let's examine where logFile.Close() is deferred and how it relates to the Fatal call
grep -A 20 "logFile.Close()" cmd/root.go
```

Length of output: 805

---



</details>

**Action Required: Revise Error Handling to Ensure Proper Resource Cleanup**

The log level validation is a solid enhancement; however, the static analysis warning ("exitAfterDefer") remains a concern. When `log.Fatal` is invoked (line 119), it immediately terminates the program, which means the deferred `logFile.Close()` (set on line 112) will not execute. This could lead to a resource leak if the log file isn’t closed properly.

**Suggested Actions:**
- **Review Error Handling:** Consider refactoring this block so that cleanup (i.e., closing the log file) occurs before the program exits. For example, explicitly call `logFile.Close()` prior to exiting or use a different error-handling strategy that ensures resources are released.
- **Validate with Static Analysis:** Re-run your static analysis tool after modifications to ensure that the "exitAfterDefer" warning is addressed.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

[failure] 119-119: [golangci] cmd/root.go#L119
exitAfterDefer: log.Fatal will exit, and `defer logFile.Close()` will not run (gocritic)

</details>

</details>

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:13:03Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:26:02Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:32:00Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T20:32:15Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T21:54:51Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T22:20:29Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T22:33:16Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T22:38:37Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T22:41:18Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T22:44:17Z
user: login: coderabbitai[bot]
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T22:47:22Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T22:47:23Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T01:15:38Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T01:16:14Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:04:45Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:09:46Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:10:13Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:10:26Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:10:37Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:10:50Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T15:11:11Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T15:14:19Z
user: login: coderabbitai[bot]
body: > [!CAUTION]
> Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.  If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

**Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>internal/exec/terraform_outputs.go (1)</summary><blockquote>

`21-39`: **Add a constant for the "error" string**

Static analysis has flagged that the string literal "error" appears at least 4 times in the code. Creating a named constant would improve maintainability.


Add a constant for the "error" string at the top of the file with the other constants:

```diff
const (
	cliArgsEnvVar            = "TF_CLI_ARGS"
	inputEnvVar              = "TF_INPUT"
	automationEnvVar         = "TF_IN_AUTOMATION"
	logEnvVar                = "TF_LOG"
	logCoreEnvVar            = "TF_LOG_CORE"
	logPathEnvVar            = "TF_LOG_PATH"
	logProviderEnvVar        = "TF_LOG_PROVIDER"
	reattachEnvVar           = "TF_REATTACH_PROVIDERS"
	appendUserAgentEnvVar    = "TF_APPEND_USER_AGENT"
	workspaceEnvVar          = "TF_WORKSPACE"
	disablePluginTLSEnvVar   = "TF_DISABLE_PLUGIN_TLS"
	skipProviderVerifyEnvVar = "TF_SKIP_PROVIDER_VERIFY"
+
+	errorKey                 = "error"
)
```

Then replace instances of the string literal "error" with this constant.

</blockquote></details>

</blockquote></details>

<details>
<summary>🛑 Comments failed to post (1)</summary><blockquote>

<details>
<summary>internal/exec/terraform_outputs.go (1)</summary><blockquote>

299-301: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Review error message handling pattern**

The code now uses direct `u.PrintMessage` calls for error handling. This approach is consistent across the file and improves the user experience by showing clear error feedback.

However, based on previous discussions, there's a concern about whether these messages go to stderr. If they go to stdout, they could corrupt JSON output and cause regressions.


Let's verify how `u.PrintMessage` is implemented:




Also applies to: 307-309, 320-322

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if PrintMessage function writes to stderr
fd -t f -e go --exec grep -l "func PrintMessage" {} \; | xargs grep -A 15 "func PrintMessage"
```

Length of output: 2530

---



</details>

**Action Required: Update Error Handling to Prevent JSON Corruption**

After verifying, we confirmed that `u.PrintMessage` (implemented in `./pkg/utils/log_utils.go`) uses `fmt.Println`, which writes to **stdout**. This means that error messages routed through `u.PrintMessage` (e.g. in lines 299-301 and also at 307-309, 320-322) will end up on stdout. This could potentially corrupt JSON output if the application expects error messages on stderr.

- **Location:** `internal/exec/terraform_outputs.go` (lines 299-301, 307-309, and 320-322)
- **Issue:** Error messages are sent to stdout via `u.PrintMessage` instead of stderr.
- **Suggestion:** Consider either modifying `u.PrintMessage` to route error output to stderr or using a dedicated error logging function (e.g. `PrintErrorInColor`), to avoid corrupting JSON outputs.

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T19:02:24Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T19:09:19Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T19:21:10Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T19:26:23Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T19:28:16Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-09T19:29:58Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T20:14:53Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>internal/exec/terraform_outputs.go (1)</summary><blockquote>

`320-321`: **Define a constant for repeated string literal.**

While the change to use stderr is good, the static analysis tool has identified that the string literal "error" is used multiple times in this file.

Consider adding a constant at the file level:

```diff
+ const errorKey = "error"
```

And then use this constant wherever "error" appears as a key in log messages.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

[failure] 321-321: [golangci] internal/exec/terraform_outputs.go#L321
add-constant: string literal "error" appears, at least, 4 times, create a named constant for it (revive)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a60caab899f9f1fb0a51a6225215736554178891 and d85a5ee9cb46554f110d6d58fde1abb00656568d.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `.golangci.yml` (1 hunks)
* `internal/exec/terraform_outputs.go` (2 hunks)
* `pkg/utils/log_utils.go` (1 hunks)
* `tests/test-cases/atmos-functions.yaml` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* .golangci.yml

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

<details>
<summary>internal/exec/terraform_outputs.go</summary>

[failure] 321-321: [golangci] internal/exec/terraform_outputs.go#L321
add-constant: string literal "error" appears, at least, 4 times, create a named constant for it (revive)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: Build (windows-latest, windows)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>pkg/utils/log_utils.go (1)</summary>

`27-29`: **Well-designed utility function for stderr output.**

This new function provides a clear way to print formatted messages to standard error, which is appropriate for user-facing console output that shouldn't interfere with data output that might be piped to other commands. The implementation correctly uses `fmt.Fprintf` with `os.Stderr`.

</details>
<details>
<summary>tests/test-cases/atmos-functions.yaml (2)</summary>

`21-25`: **Tests correctly updated to match implementation change.**

The test case now expects output in `stderr` instead of `stdout`, which aligns with the implementation change in `terraform_outputs.go` where messages are now directed to stderr via `PrintMessageToStderr`.

---

`42-45`: **Consistent update for no-tty test case.**

Similar to the previous test case, this no-tty test variant has been updated to expect output in `stderr` instead of `stdout`, maintaining consistency across test cases.

</details>
<details>
<summary>internal/exec/terraform_outputs.go (3)</summary>

`299-300`: **Appropriate use of stderr for error indication.**

Using stderr for user-facing console output is the right approach. The carriage return (`\r`) before the message allows it to replace any existing spinner text on the same line.

---

`307-308`: **Consistent error reporting.**

This follows the same pattern as the previous error message, maintaining consistency in how errors are reported to the user.

---

`328-328`: **Success indication matches error reporting style.**

Good use of the checkmark symbol to indicate success, matching the style of error reporting with the X symbol. Directing this to stderr keeps the stdout clean for actual data output.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T20:51:37Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>internal/exec/terraform_outputs.go (1)</summary><blockquote>

`321-321`: **Consider adding a constant for "error" string**

The static analysis tool detected that the string literal "error" appears at least 4 times in this file. Creating a named constant would follow Go best practices.

```diff
+const errorKey = "error"
```

Then use it throughout the file:
```diff
-l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, errorKey, err)
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

[failure] 321-321: [golangci] internal/exec/terraform_outputs.go#L321
add-constant: string literal "error" appears, at least, 4 times, create a named constant for it (revive)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d85a5ee9cb46554f110d6d58fde1abb00656568d and 91b562b3870c0b4d98e980dbc36a72c0e61e6097.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `.golangci.yml` (1 hunks)
* `internal/exec/terraform_outputs.go` (2 hunks)
* `pkg/utils/log_utils.go` (1 hunks)
* `tests/test-cases/atmos-functions.yaml` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* pkg/utils/log_utils.go
* tests/test-cases/atmos-functions.yaml
* .golangci.yml

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

<details>
<summary>internal/exec/terraform_outputs.go</summary>

[failure] 321-321: [golangci] internal/exec/terraform_outputs.go#L321
add-constant: string literal "error" appears, at least, 4 times, create a named constant for it (revive)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: Build (windows-latest, windows)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>internal/exec/terraform_outputs.go (4)</summary>

`298-301`: **Error handling streamlined for better clarity**

Good job simplifying the error reporting pattern. Using `PrintMessageToStderr` makes the error handling more consistent and ensures the error message appears properly on stderr without conditional logic.

---

`306-309`: **Consistent error pattern applied here as well**

This follows the same pattern as the previous error handling block. The removal of conditional logging checks helps maintain a cleaner codebase.

---

`319-322`: **Error handling consistency maintained**

The third instance of this pattern maintains the same approach to error reporting. This consistency across error scenarios improves code readability.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

[failure] 321-321: [golangci] internal/exec/terraform_outputs.go#L321
add-constant: string literal "error" appears, at least, 4 times, create a named constant for it (revive)

</details>

</details>

---

`328-328`: **Success messaging now matches error pattern**

Good decision to use the same `PrintMessageToStderr` function for success messages. This ensures all console output from this operation goes to stderr, preventing potential corruption of JSON output on stdout.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T21:02:39Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T21:42:55Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T21:43:26Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T21:44:38Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>cmd/root.go (1)</summary><blockquote>

`177-190`: _🛠️ Refactor suggestion_

**Remove redundant log configuration code.**

This section sets log configuration from environment variables and command line flags, but this is redundant with the changes made in `setupLogger`. The `InitCliConfig` should have already populated `atmosConfig` from environment variables.

The PR objective states "Remove unused flag code in root" and mentions that "the values are already set elsewhere in the code", which aligns with removing this section.


```diff
-	// TODO: This is a quick patch to mitigate the issue we can look for better code later
-	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
-		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
-	}
-	flagKeyValue, _ := parseFlags(os.Args)
-	if v, ok := flagKeyValue["logs-level"]; ok {
-		atmosConfig.Logs.Level = v
-	}
-	if os.Getenv("ATMOS_LOGS_FILE") != "" {
-		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
-	}
-	if v, ok := flagKeyValue["logs-file"]; ok {
-		atmosConfig.Logs.File = v
-	}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0bc023b6852718960544bfa2ba934ffbedbecba0 and 4234555aeb9a806462d83f13eb3816dd49502622.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `cmd/root.go` (2 hunks)
* `internal/exec/terraform_outputs.go` (2 hunks)
* `pkg/utils/log_utils.go` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* internal/exec/terraform_outputs.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

<details>
<summary>cmd/root.go</summary>

[failure] 118-118: [golangci] cmd/root.go#L118
directive `//nolint:all The reason to escape this is because it is expected to fail fast and` should match `//nolint[:<comma-separated-linters>] [// <explanation>]` (nolintlint)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Build (windows-latest, windows)
* GitHub Check: [lint] golangci
* GitHub Check: Analyze (go)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>cmd/root.go (2)</summary>

`20-20`: **Clean import addition.**

The addition of the logger package import is appropriate for the updated logging configuration.

---

`121-121`: **Good debug logging.**

The debug log includes appropriate information about the configured log level and file. This will be helpful for troubleshooting.

</details>
<details>
<summary>pkg/utils/log_utils.go (2)</summary>

`38-41`: **Good addition of PrintfMessageToTUI function.**

This function provides a clear way to print formatted messages to stderr, which is appropriate for TUI interactions. The implementation is clean and straightforward.

---

`44-44`: **Good comment improvement.**

Adding the period at the end of the comment improves consistency in the codebase.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T21:48:26Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T22:11:00Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T22:17:13Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T23:19:22Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T02:13:00Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T02:14:47Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T19:10:15Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T19:25:15Z
user: login: osterman
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-15T00:46:02Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T21:00:00Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>cmd/root.go (1)</summary><blockquote>

`117-119`: **Narrow the lint directive if possible.**
Using `//nolint:all` is broad. Consider specifying the exact linter(s) or refactoring so that resource closures are addressed before `log.Fatal()`.

</blockquote></details>
<details>
<summary>pkg/config/config.go (1)</summary><blockquote>

`395-410`: **Validate precedence of env vs. flags.**
Reflexively updating `atmosConfig.Logs` from both environment variables and flags might lead to confusion. Confirm that the current priority (env first, then flags) suits your needs.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 06dfacddab1b1f08b7a54ac656be92b307fbe118 and ab7d41651c7f808e5271e1da6409986cb782a6a7.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `cmd/root.go` (2 hunks)
* `pkg/config/config.go` (2 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Build (windows-latest, windows)
* GitHub Check: Lint (golangci)
* GitHub Check: Analyze (go)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>cmd/root.go (2)</summary>

`20-20`: **Import usage looks good.**
This import is utilized by subsequent logic for parsing log levels.

---

`121-121`: **Helpful debug message.**
The debug logging is a good addition to confirm the effective log level and file.

</details>
<details>
<summary>pkg/config/config.go (2)</summary>

`10-10`: **Import statement is clear.**
The new `strings` import is properly referenced by manual flag parsing logic.

---

`389-389`: **Initialization placement is appropriate.**
Calling `setLogConfig` after config processing ensures local overrides are properly applied.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:29:21Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:29:48Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:31:30Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:31:40Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:31:54Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:35:01Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>pkg/config/config.go (3)</summary><blockquote>

`395-410`: **The log configuration logic looks correct but could benefit from refactoring.**

The function appropriately checks environment variables and command-line flags for logging configuration. The priority order (flags override environment variables) is logical.

However, the TODO comment indicates this is a quick patch. Consider revisiting this in a future PR to integrate with Cobra's built-in flag handling system.

---

`418-418`: **Add period at end of comment.**

The static code analyzer correctly flagged this missing period at the end of the comment.

```diff
-// Function to manually parse flags with double dash "--" like Cobra
+// Function to manually parse flags with double dash "--" like Cobra.
```

---

`419-447`: **Flag parsing implementation could be simplified.**

The flag parsing logic works correctly but could be simplified as suggested by static analysis tools.


Consider these improvements:
1. Simplify the if-else chain using early returns
2. Use a switch statement for better readability
3. Return a potential error for better error handling

```diff
 func parseFlags() map[string]string {
 	args := os.Args
 	flags := make(map[string]string)
 	for i := 0; i < len(args); i++ {
 		arg := args[i]

 		// Check if the argument starts with '--' (double dash)
 		if strings.HasPrefix(arg, "--") {
 			// Strip the '--' prefix and check if it's followed by a value
 			arg = arg[2:]
-			if strings.Contains(arg, "=") {
-				// Case like --flag=value
-				parts := strings.SplitN(arg, "=", 2)
-				flags[parts[0]] = parts[1]
-			} else if i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") {
-				// Case like --flag value
-				flags[arg] = args[i+1]
-				i++ // Skip the next argument as it's the value
-			} else {
-				// Case where flag has no value, e.g., --flag (we set it to "true")
-				flags[arg] = "true"
-			}
-		} else {
-			// It's a regular argument, not a flag, so we skip
-			continue
+			
+			switch {
+			case strings.Contains(arg, "="):
+				// Case like --flag=value
+				parts := strings.SplitN(arg, "=", 2)
+				flags[parts[0]] = parts[1]
+			case i+1 < len(args) && !strings.HasPrefix(args[i+1], "--"):
+				// Case like --flag value
+				flags[arg] = args[i+1]
+				i++ // Skip the next argument as it's the value
+			default:
+				// Case where flag has no value, e.g., --flag (we set it to "true")
+				flags[arg] = "true"
+			}
+			continue
 		}
+		
+		// It's a regular argument, not a flag, so we skip
 	}
 	return flags
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ab7d41651c7f808e5271e1da6409986cb782a6a7 and 4ee23af8105cca50d476e8910fc10dc71d0fd664.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `cmd/root.go` (2 hunks)
* `pkg/config/config.go` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* cmd/root.go

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Build (windows-latest, windows)
* GitHub Check: Analyze (go)
* GitHub Check: Lint (golangci)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>pkg/config/config.go (4)</summary>

`10-10`: **Appropriate import addition.**

New import aligns with the strings manipulation needs in `parseFlags` function.

---

`389-389`: **Good integration of log configuration.**

Calling `setLogConfig` at the end of `InitCliConfig` ensures log settings are applied after all other configuration steps, centralizing the logging configuration.

---

`412-417`: **Valid concern about manual flag parsing.**

The TODO correctly acknowledges that manual flag parsing is not ideal when using Cobra. This approach works for now, but should be revisited in a future refactoring effort.

---

`419-447`: <details>
<summary>✅ Verification successful</summary>

**Verify this function handles all use cases in the codebase.**

The `parseFlags` function assumes all flags start with `--`, but doesn't handle short-form flags (`-f`). Ensure this matches all flag usage patterns in the codebase.

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Search for flag pattern usage in the codebase to verify compatibility

# Search for short flags (-f) in the codebase
echo "Searching for short flags that might not be handled by parseFlags..."
rg "flag\.(StringP|BoolP|IntP)" --type go

# Check if Cobra is being used for command-line parsing
echo "Checking for Cobra command-line parsing..."
rg "cobra\.(Command|OnInitialize|AddCommand)" --type go

# Check how flags are defined in the codebase
echo "Checking how flags are registered in the codebase..."
rg "flag\.(String|Bool|Int)" --type go

# Specifically check for logs-level and logs-file flags
echo "Checking for logs-level and logs-file flags..."
rg "(logs-level|logs-file)" --type go
```

Length of output: 15536

---



</details>

**Flag Parsing Approach Verified – Only Long Flags in Use**

After a thorough search of our codebase:
- There are no instances of short-form flag usage (like `-f`) detected.
- Flags registered using Go’s `flag` package consistently use long names (e.g., `"--logs-level"`), matching the expectations of the `parseFlags` function.
- Cobra is employed extensively for command parsing elsewhere, and it manages both long and short flags; however, `parseFlags` appears to be designed only for contexts where long flags are in use.

**Recommendation:**  
Add a brief comment to the `parseFlags` function explaining that it is intended solely for long flag formats (e.g., `--flag=value` or `--flag value`), clarifying its limitations for any future developers.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:35:27Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:35:30Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T14:42:42Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T02:03:13Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T02:04:02Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T02:09:28Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T02:09:55Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T07:53:27Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>pkg/config/config.go (2)</summary><blockquote>

`396-411`: **Temporary logging configuration solution needs better documentation.**

The function properly centralizes log configuration from both environment variables and command line flags, which is architecturally sound. However, the TODO comment doesn't clearly explain what issue this is patching.


```diff
-// TODO: This is a quick patch to mitigate the issue we can look for better code later
+// TODO: This is a quick patch to centralize log configuration handling that was previously 
+// scattered across the codebase. In the future, we should refactor this to use a more 
+// structured approach that integrates better with Cobra's flag handling.
```

---

`419-446`: **Manual flag parsing implementation works but has limitations.**

This manual flag parsing implementation is functional for basic cases but could become a maintenance burden. It works well for the current use case of extracting logging flags, but be cautious about extending this approach.


Consider limiting the scope of this function to only parse specific logging-related flags rather than all double-dash arguments, which would reduce potential conflicts with Cobra:

```diff
-func parseFlags() map[string]string {
+func parseLoggingFlags() map[string]string {
   args := os.Args
   flags := make(map[string]string)
+  loggingFlags := []string{"logs-level", "logs-file"}
   for i := 0; i < len(args); i++ {
     arg := args[i]
     // Check if the argument starts with '--' (double dash)
     if !strings.HasPrefix(arg, "--") {
       continue
     }
     // Strip the '--' prefix and check if it's followed by a value
     arg = arg[2:]
+    // Only process logging-related flags
+    isLoggingFlag := false
+    for _, flagName := range loggingFlags {
+      if arg == flagName || strings.HasPrefix(arg, flagName+"=") {
+        isLoggingFlag = true
+        break
+      }
+    }
+    if !isLoggingFlag {
+      continue
+    }
     switch {
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b7047e5e4f67d57ec9a2f0bc38d725cdf9e754ee and 6bddcfe6ad9ccba0072bec6193ba14292c5dc3a8.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `pkg/config/config.go` (2 hunks)
* `pkg/config/utils.go` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: Lint (golangci)
* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Analyze (go)
* GitHub Check: Lint (golangci)
* GitHub Check: Build (windows-latest, windows)
* GitHub Check: Analyze (go)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>pkg/config/utils.go (1)</summary>

`361-361`: **Consistent logging format update.**

The change from `u.LogDebug(fmt.Sprintf(...))` to structured logging with `log.Debug(..., ...)` is a good improvement that aligns with modern logging practices.

</details>
<details>
<summary>pkg/config/config.go (2)</summary>

`390-390`: **Good centralization of log configuration.**

This call to `setLogConfig` finalizes the log configuration after all other configurations have been processed, ensuring a consistent approach to logging setup.

---

`413-418`: **The TODOs accurately acknowledge the flag parsing concerns.**

The comments correctly identify that manual flag parsing isn't ideal compared to leveraging Cobra's built-in capabilities.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:20:04Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:20:07Z
user: login: samtholiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:55:06Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>pkg/config/config.go (3)</summary><blockquote>

`397-398`: **Consider improving the TODO comment clarity.**

The current TODO comment references an issue without explaining what the actual issue is. This makes it harder for future developers to understand the context without access to the issue tracking system.

```diff
-	// TODO: This is a quick patch to mitigate the issue we can look for better code later
-	// Issue: https://linear.app/cloudposse/issue/DEV-3093/create-a-cli-command-core-library
+	// TODO: This is a quick patch to handle log configuration from both environment variables and CLI flags.
+	// A more comprehensive solution will be implemented as part of the CLI command core library.
+	// Issue: https://linear.app/cloudposse/issue/DEV-3093/create-a-cli-command-core-library
```

---

`421-447`: **Consider adding error handling to the flag parsing function.**

The current implementation of `parseFlags()` makes certain assumptions about the format of command-line arguments, but doesn't handle potential errors or edge cases such as duplicate flags.

```diff
-func parseFlags() map[string]string {
+func parseFlags() (map[string]string, error) {
 	args := os.Args
 	flags := make(map[string]string)
 	for i := 0; i < len(args); i++ {
 		arg := args[i]
 		// Check if the argument starts with '--' (double dash)
 		if !strings.HasPrefix(arg, "--") {
 			continue
 		}
 		// Strip the '--' prefix and check if it's followed by a value
 		arg = arg[2:]
+		// Check if flag name is empty
+		if len(arg) == 0 {
+			return nil, fmt.Errorf("empty flag name found at position %d", i)
+		}
 		switch {
 		case strings.Contains(arg, "="):
 			// Case like --flag=value
 			parts := strings.SplitN(arg, "=", 2)
+			if _, exists := flags[parts[0]]; exists {
+				return nil, fmt.Errorf("duplicate flag: %s", parts[0])
+			}
 			flags[parts[0]] = parts[1]
 		case i+1 < len(args) && !strings.HasPrefix(args[i+1], "--"):
 			// Case like --flag value
+			if _, exists := flags[arg]; exists {
+				return nil, fmt.Errorf("duplicate flag: %s", arg)
+			}
 			flags[arg] = args[i+1]
 			i++ // Skip the next argument as it's the value
 		default:
 			// Case where flag has no value, e.g., --flag (we set it to "true")
+			if _, exists := flags[arg]; exists {
+				return nil, fmt.Errorf("duplicate flag: %s", arg)
+			}
 			flags[arg] = "true"
 		}
 	}
-	return flags
+	return flags, nil
 }
```

This would require updating the calling code in `setLogConfig`:

```diff
-	flagKeyValue := parseFlags()
+	flagKeyValue, err := parseFlags()
+	if err != nil {
+		u.LogWarning(fmt.Sprintf("error parsing flags: %v", err))
+		flagKeyValue = make(map[string]string)
+	}
```

---

`402-405`: **Consider consistent error handling for flag parsing results.**

Currently, the code silently accepts whatever value is provided for `logs-level` without validation. Consider adding validation to ensure the value is a valid log level.

```diff
 	flagKeyValue := parseFlags()
 	if v, ok := flagKeyValue["logs-level"]; ok {
+		// Validate log level before setting
+		if _, err := u.ParseLogLevel(v); err != nil {
+			u.LogWarning(fmt.Sprintf("invalid log level: %s", v))
+		} else {
 		atmosConfig.Logs.Level = v
+		}
 	}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6bddcfe6ad9ccba0072bec6193ba14292c5dc3a8 and 9e70a8c605ff46d3d1a9ad9f9516afe1ba632822.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `pkg/config/config.go` (2 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Build (windows-latest, windows)
* GitHub Check: Analyze (go)
* GitHub Check: Lint (golangci)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>pkg/config/config.go (3)</summary>

`390-390`: **Good structuring by centralizing log config setting.**

The addition of this function call ensures that logging configuration happens at a consistent point in the initialization flow, after all other config sources have been processed.

---

`414-419`: **Good use of TODO comments for technical debt tracking.**

The comments clearly document why manual flag parsing is being used and the intention to revisit this approach in the future.

---

`396-412`: <details>
<summary>✅ Verification successful</summary>

**The log config priority logic is clear and straightforward.**

The implementation properly handles environment variables and command-line flags for logging configuration, with flags taking priority over environment variables.


Let's verify if there are any calls elsewhere in the codebase that might be setting these same values:

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for other code that might be setting log levels or files
rg -p "atmosConfig.Logs.(Level|File) ?=" --type go
```

Length of output: 2769

---



</details>

**Log configuration priority verified across codebase**

The log configuration function in `pkg/config/config.go` correctly applies environment variables followed by flag overrides. Our review of the repository shows that while several tests and utility files (such as those in `pkg/workflow/workflow_test.go`, `pkg/vender/*`, and various files under `internal/exec/`) reference or assign log levels for validation purposes, none of them conflict with the priority logic established in this function.

- The tests and internal utilities typically compare against or set a default log level (e.g., `u.LogLevelTrace`), which verifies the expected behavior rather than interfering with the configuration ordering.
- This consistent usage confirms that the current implementation is safe and effective.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:58:37Z
user: login: coderabbitai[bot]
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:58:40Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T21:17:12Z
user: login: osterman
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T21:17:59Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T21:19:36Z
user: login: osterman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T21:22:16Z

Review comments:
diff_hunk: @@ -1,7 +1,6 @@
+DEBU Log level set to DEBU file=/dev/stdout
path: tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
user: login: osterman
body: We should convey where this value came from or how do we know it’s working as intended?
created_at: 2025-03-01T15:49:43Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,6 @@
+DEBU Log level set to DEBU file=/dev/stdout
path: tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
user: login: samtholiya
body: we know it is working as intended because of test cases as of now. 
created_at: 2025-03-01T15:51:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,6 +114,7 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	log.Debug("Log level set to", "level", log.GetLevel(), "file", atmosConfig.Logs.File)
path: cmd/root.go
user: login: osterman
body: Weird the “level=DEBU” is not in the output.
created_at: 2025-03-01T15:53:08Z
author_association: MEMBER
diff_hunk: @@ -124,6 +114,7 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	log.Debug("Log level set to", "level", log.GetLevel(), "file", atmosConfig.Logs.File)
path: cmd/root.go
user: login: samtholiya
body: Yep that was what i thought as well. Do we want to dig into it?

created_at: 2025-03-01T16:02:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,6 +114,7 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	log.Debug("Log level set to", "level", log.GetLevel(), "file", atmosConfig.Logs.File)
path: cmd/root.go
user: login: osterman
body: Yes, might be worth an investigation. Anytime something doesn't work as expected, we should wonder why. 
created_at: 2025-03-02T13:28:37Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,4 @@
+DEBU Log level set to DEBU file=/dev/stderr
 DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
path: tests/snapshots/TestCLICommands_Valid_log_level_in_env_should_be_priortized_over_config.stderr.golden
user: login: osterman
body: What concerns me is that we emit found after we emit set. Something seems off. For example, if we said "ignoring ENV" I would understand. Found implies we will use it. 
created_at: 2025-03-02T13:33:28Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,4 @@
+DEBU Log level set to DEBU file=/dev/stderr
 DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
path: tests/snapshots/TestCLICommands_Valid_log_level_in_env_should_be_priortized_over_config.stderr.golden
user: login: samtholiya
body: Working on this :)
created_at: 2025-03-03T21:07:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,19 +296,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: No, this is console output, and shouldn't be considered log output. 
created_at: 2025-03-05T14:04:15Z
author_association: MEMBER
diff_hunk: @@ -296,19 +296,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
 		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: I think the original implementation was wrong to use log levels.

This is console output, versus log output. 

We should probably have a function dedicated to console output, so it's a deliberate thing, versus the fmt.Printf.

Log Output: Primarily meant for developers, operations teams, and debugging. It captures events, errors, and system state based on log levels (DEBUG, INFO, WARN, ERROR, etc.).

Console Output: Primarily meant for users, especially in interactive or CLI applications. It presents structured UI elements, progress indicators, tables, or other human-readable content.

The "x" is the giveaway this console output and not log output. Console output is not governed by log levels.
created_at: 2025-03-05T14:09:01Z
author_association: MEMBER
diff_hunk: @@ -296,19 +296,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
 		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: Thanks for the clarification
created_at: 2025-03-05T21:21:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,19 +296,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: Done
created_at: 2025-03-06T20:32:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,19 +296,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
 		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		l.Debug(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: Done
created_at: 2025-03-06T20:32:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1 +1 @@
-ERRO Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]
+FATA Error parsing log level error="Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]"
path: tests/snapshots/TestCLICommands_Invalid_Log_Level_in_Environment_Variable.stderr.golden
user: login: osterman
body: I don't like this wrapped output
created_at: 2025-03-07T22:09:33Z
author_association: MEMBER
diff_hunk: @@ -123,6 +114,11 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all
+		log.Fatal("Error parsing log level", "error", err)
path: cmd/root.go
user: login: osterman
body: ```suggestion
		log.Fatal(err)
```

created_at: 2025-03-07T22:12:40Z
author_association: MEMBER
diff_hunk: @@ -123,6 +114,11 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all
+		log.Fatal("Error parsing log level", "error", err)
+	}
+	log.Debug("Log level set to", "level", log.GetLevel(), "file", atmosConfig.Logs.File)
path: cmd/root.go
user: login: osterman
body: ```suggestion
	log.Debug("set", "log-level", log.GetLevel(), "log-file", atmosConfig.Logs.File)
```

It turns out that level is an undocumented reserved word. https://github.com/charmbracelet/log/issues/107
created_at: 2025-03-07T22:14:49Z
author_association: MEMBER
diff_hunk: @@ -1 +1 @@
-ERRO Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]
+FATA Error parsing log level error="Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]"
path: tests/snapshots/TestCLICommands_Invalid_Log_Level_in_Environment_Variable.stderr.golden
user: login: samtholiya
body: Done
created_at: 2025-03-07T22:33:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -123,6 +114,11 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all
+		log.Fatal(err)
+	}
+	log.Debug("set", "log-level", log.GetLevel(), "log-file", atmosConfig.Logs.File)
path: cmd/root.go
user: login: osterman
body: ```suggestion
	log.Debug("Set", "logs-level", log.GetLevel(), "logs-file", atmosConfig.Logs.File)
```
For consistency 
created_at: 2025-03-07T22:40:07Z
author_association: MEMBER
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, errorKey, err)
 		}
 
 		// Cache the result
 		terraformOutputsCache.Store(stackSlug, terraformOutputs)
 		result = getTerraformOutputVariable(atmosConfig, component, stack, terraformOutputs, output)
 	}
-
-	if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-		// Show success
-		fmt.Printf("\r✓ %s\n", message)
-	}
+	u.PrintMessage(fmt.Sprintf("\r✓ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: Does this go to stderr?
created_at: 2025-03-07T22:41:58Z
author_association: MEMBER
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: Does this go to stderr?
created_at: 2025-03-07T22:42:23Z
author_association: MEMBER
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+		l.Fatal("Failed to describe the component", "component", component, "stack", stack, errorKey, err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: Does this go to stderr?
created_at: 2025-03-07T22:42:33Z
author_association: MEMBER
diff_hunk: @@ -36,6 +36,7 @@ const (
 
 	varEnvVarPrefix    = "TF_VAR_"
 	cliArgEnvVarPrefix = "TF_CLI_ARGS_"
+	errorKey           = "error"
path: internal/exec/terraform_outputs.go
user: login: osterman
body: No, add this instead to the .golangci-lint.yaml config as an acceptable string to repeat.
created_at: 2025-03-07T22:43:38Z
author_association: MEMBER
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: Does this go to stderr?
created_at: 2025-03-07T22:43:55Z
author_association: MEMBER
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, errorKey, err)
 		}
 
 		// Cache the result
 		terraformOutputsCache.Store(stackSlug, terraformOutputs)
 		result = getTerraformOutputVariable(atmosConfig, component, stack, terraformOutputs, output)
 	}
-
-	if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-		// Show success
-		fmt.Printf("\r✓ %s\n", message)
-	}
+	u.PrintMessage(fmt.Sprintf("\r✓ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: Can we modify print message to support format specifiers so we don’t need to wrap sprintf?
created_at: 2025-03-09T01:15:37Z
author_association: MEMBER
diff_hunk: @@ -123,6 +114,11 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all
path: cmd/root.go
user: login: osterman
body: Why are we no linting here?
created_at: 2025-03-09T01:16:14Z
author_association: MEMBER
diff_hunk: @@ -123,6 +114,11 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all
path: cmd/root.go
user: login: samtholiya
body: We get error that we should only exit in main and init. If we ignore that we get the error that we should wait for the the defer function before exiting.
created_at: 2025-03-09T15:04:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, errorKey, err)
 		}
 
 		// Cache the result
 		terraformOutputsCache.Store(stackSlug, terraformOutputs)
 		result = getTerraformOutputVariable(atmosConfig, component, stack, terraformOutputs, output)
 	}
-
-	if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-		// Show success
-		fmt.Printf("\r✓ %s\n", message)
-	}
+	u.PrintMessage(fmt.Sprintf("\r✓ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: >Does this go to stderr?

https://github.com/cloudposse/atmos/blob/main/tests/test-cases/atmos-functions.yaml#L21-L26
Based on above test case we do not add the line to stderr.

> Can we modify print message to support format specifiers so we don’t need to wrap sprintf?

yep we can do that. On it
created_at: 2025-03-09T15:09:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: Does this go to stderr?

https://github.com/cloudposse/atmos/blob/main/tests/test-cases/atmos-functions.yaml#L21-L26
Based on above test case we do not add the line to stderr.
created_at: 2025-03-09T15:10:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+		l.Fatal("Failed to describe the component", "component", component, "stack", stack, errorKey, err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: >Does this go to stderr?

https://github.com/cloudposse/atmos/blob/main/tests/test-cases/atmos-functions.yaml#L21-L26
Based on above test case we do not add the line to stderr.
created_at: 2025-03-09T15:10:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: > Does this go to stderr?

https://github.com/cloudposse/atmos/blob/main/tests/test-cases/atmos-functions.yaml#L21-L26
Based on above test case we do not add the line to stderr.
created_at: 2025-03-09T15:10:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,6 +36,7 @@ const (
 
 	varEnvVarPrefix    = "TF_VAR_"
 	cliArgEnvVarPrefix = "TF_CLI_ARGS_"
+	errorKey           = "error"
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: okay cool
created_at: 2025-03-09T15:10:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -123,6 +114,11 @@ func setupLogger(atmosConfig *schema.AtmosConfiguration) {
 	}
 
 	log.SetOutput(output)
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all
path: cmd/root.go
user: login: osterman
body: Please add a comment with that explanation. Also, this undermines defer and why we should (not now) fix it.
created_at: 2025-03-09T15:11:11Z
author_association: MEMBER
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, errorKey, err)
 		}
 
 		// Cache the result
 		terraformOutputsCache.Store(stackSlug, terraformOutputs)
 		result = getTerraformOutputVariable(atmosConfig, component, stack, terraformOutputs, output)
 	}
-
-	if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-		// Show success
-		fmt.Printf("\r✓ %s\n", message)
-	}
+	u.PrintMessage(fmt.Sprintf("\r✓ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: If output to stdout, this will break atmos ability to use terraform outputs because the json will get corrupted by this printing to stdout, causing a regression that we just (incorrectly) fixed. It should be fixed by outputting the console output to stderr, so stdout is the unmodified json. 
created_at: 2025-03-09T15:14:19Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,4 @@
+DEBU Log level set to DEBU file=/dev/stderr
 DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug
 DEBU Found ENV var ATMOS_VERSION_CHECK_ENABLED=false
 DEBU processStoreConfig atmosConfig.StoresConfig=map[]
path: tests/snapshots/TestCLICommands_Valid_log_level_in_env_should_be_priortized_over_config.stderr.golden
user: login: samtholiya
body: Done
created_at: 2025-03-09T19:09:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: There are 3 problems with this:
1) engineer that implemented it emitted it to stdout, which broke all of our GitHub actions
2) engineer that "fixed" it changed it to only emit on debug, which still break our GitHub actions, just it's not the default log level
3) the whole purpose of this is not for logging or debugging, so (2) broke the usefulness of the feature

The appropriate fix is to emit output that is part of TUI to stderr so stdout is untarnished.
created_at: 2025-03-09T19:21:10Z
author_association: MEMBER
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+		l.Fatal("Failed to describe the component", "component", component, "stack", stack, errorKey, err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: Ok, this further drives home the point that we should have an output function named after its purpose, because engineers don't understand generally when to use stderr versus stdout.

Maybe PrintData (stdout), and PrintConsole (stderr). 
created_at: 2025-03-09T19:26:22Z
author_association: MEMBER
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+		l.Fatal("Failed to describe the component", "component", component, "stack", stack, errorKey, err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: osterman
body: And we need a good description of each function and when to use it
created_at: 2025-03-09T19:28:16Z
author_association: MEMBER
diff_hunk: @@ -74,7 +74,7 @@ tests:
       stdout:
         - '^\n👽 Atmos (\d+\.\d+\.\d+|test) on [a-z]+/[a-z0-9_]+\n\n'
       stderr:
-        - "DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug"
+        - "Set logs-level=debug logs-file=/dev/stderr"
path: tests/test-cases/log-level-validation.yaml
user: login: osterman
body: Do we have a test that tests the preference order of config, env, and flags?
created_at: 2025-03-09T19:29:53Z
author_association: MEMBER
diff_hunk: @@ -74,7 +74,7 @@ tests:
       stdout:
         - '^\n👽 Atmos (\d+\.\d+\.\d+|test) on [a-z]+/[a-z0-9_]+\n\n'
       stderr:
-        - "DEBU Found ENV var ATMOS_LOGS_LEVEL=Debug"
+        - "Set logs-level=debug logs-file=/dev/stderr"
path: tests/test-cases/log-level-validation.yaml
user: login: samtholiya
body: yes we already added that when we faced it previously. So we are good with that.
created_at: 2025-03-10T20:14:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, errorKey, err)
 		}
 
 		// Cache the result
 		terraformOutputsCache.Store(stackSlug, terraformOutputs)
 		result = getTerraformOutputVariable(atmosConfig, component, stack, terraformOutputs, output)
 	}
-
-	if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-		// Show success
-		fmt.Printf("\r✓ %s\n", message)
-	}
+	u.PrintMessage(fmt.Sprintf("\r✓ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: Done
created_at: 2025-03-11T21:42:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -321,21 +318,15 @@ func GetTerraformOutput(
 		// Execute `terraform output`
 		terraformOutputs, err := execTerraformOutput(atmosConfig, component, stack, sections)
 		if err != nil {
-			if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-				fmt.Printf("\r✗ %s\n", message)
-			}
-			l.Fatal("Failed to execute terraform output", "component", component, "stack", stack, "error", err)
+			u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: Done
created_at: 2025-03-11T21:43:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,19 +297,15 @@ func GetTerraformOutput(
 
 	sections, err := ExecuteDescribeComponent(component, stack, true, true, nil)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
-		l.Fatal("Failed to describe the component", "component", component, "stack", stack, "error", err)
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
+		l.Fatal("Failed to describe the component", "component", component, "stack", stack, errorKey, err)
 	}
 
 	// Check if the component in the stack is configured with the 'static' remote state backend, in which case get the
 	// `output` from the static remote state instead of executing `terraform output`
 	remoteStateBackendStaticTypeOutputs, err := GetComponentRemoteStateBackendStaticType(sections)
 	if err != nil {
-		if atmosConfig.Logs.Level == u.LogLevelTrace || atmosConfig.Logs.Level == u.LogLevelDebug {
-			fmt.Printf("\r✗ %s\n", message)
-		}
+		u.PrintMessage(fmt.Sprintf("\r✗ %s\n", message))
path: internal/exec/terraform_outputs.go
user: login: samtholiya
body: okay
created_at: 2025-03-11T21:44:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -123,6 +114,11 @@
 	}
 
 	log.SetOutput(output)
+	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
+		//nolint:all The reason to escape this is because it is expected to fail fast and
+		log.Fatal(err)
+	}
path: cmd/root.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix the nolint directive format.**

The current nolint directive doesn't follow the required format and will trigger linting errors. The static analysis tool indicates it should match `//nolint[:<comma-separated-linters>] [// <explanation>]`.


Apply this fix:
```diff
-	//nolint:all The reason to escape this is because it is expected to fail fast and
+	//nolint:all // The reason to escape this is because it is expected to fail fast and
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
		//nolint:all // The reason to escape this is because it is expected to fail fast and
		log.Fatal(err)
	}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: golangci</summary>

[failure] 118-118: [golangci] cmd/root.go#L118
directive `//nolint:all The reason to escape this is because it is expected to fail fast and` should match `//nolint[:<comma-separated-linters>] [// <explanation>]` (nolintlint)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-11T21:48:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,8 +35,13 @@ func PrintErrorInColor(message string) {
 	_, _ = messageColor.Fprint(os.Stderr, message)
 }
 
+// PrintfMessageToTUI prints the message to the stderr.
+func PrintfMessageToTUI(message string, args ...any) {
path: pkg/utils/log_utils.go
user: login: osterman
body: This function doesn't belong in `log_utils`, which should be about logging. 

Actually, none of these belong here, because they are related to TUI.

- PrintMessage(message string)
- PrintMessageInColor(message string, messageColor *color.Color)
- PrintErrorInColor(message string)
- PrintfMessageToTUI(message string, args ...any)



created_at: 2025-03-14T02:13:00Z
author_association: MEMBER
diff_hunk: @@ -35,8 +35,13 @@ func PrintErrorInColor(message string) {
 	_, _ = messageColor.Fprint(os.Stderr, message)
 }
 
+// PrintfMessageToTUI prints the message to the stderr.
+func PrintfMessageToTUI(message string, args ...any) {
path: pkg/utils/log_utils.go
user: login: osterman
body: Maybe we can open a new PR that uses the new, non-deprecated functions. Then move `PrintfMessageToTUI` to a TUI package, so we can call `tui.Printf`.
created_at: 2025-03-14T02:14:46Z
author_association: MEMBER
diff_hunk: @@ -35,8 +35,13 @@ func PrintErrorInColor(message string) {
 	_, _ = messageColor.Fprint(os.Stderr, message)
 }
 
+// PrintfMessageToTUI prints the message to the stderr.
+func PrintfMessageToTUI(message string, args ...any) {
path: pkg/utils/log_utils.go
user: login: samtholiya
body: Yep that makes sense to me but yaa let us create a new pr for this one. I am creating a ticket to do thsi and linking it here.
created_at: 2025-03-14T19:10:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,8 +35,13 @@ func PrintErrorInColor(message string) {
 	_, _ = messageColor.Fprint(os.Stderr, message)
 }
 
+// PrintfMessageToTUI prints the message to the stderr.
+func PrintfMessageToTUI(message string, args ...any) {
path: pkg/utils/log_utils.go
user: login: samtholiya
body: https://linear.app/cloudposse/issue/DEV-3091/move-logging-to-tui-package
created_at: 2025-03-14T19:25:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue, _ := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
+func parseFlags() (map[string]string, error) {
+	args := os.Args
+	flags := make(map[string]string)
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+
+		// Check if the argument starts with '--' (double dash)
+		if strings.HasPrefix(arg, "--") {
+			// Strip the '--' prefix and check if it's followed by a value
+			arg = arg[2:]
+			if strings.Contains(arg, "=") {
+				// Case like --flag=value
+				parts := strings.SplitN(arg, "=", 2)
+				flags[parts[0]] = parts[1]
+			} else if i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") {
+				// Case like --flag value
+				flags[arg] = args[i+1]
+				i++ // Skip the next argument as it's the value
+			} else {
+				// Case where flag has no value, e.g., --flag (we set it to "true")
+				flags[arg] = "true"
+			}
+		} else {
+			// It's a regular argument, not a flag, so we skip
+			continue
+		}
+	}
+	return flags, nil
+}
path: pkg/config/config.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider leveraging Cobra’s built-in flag parsing.**
Manually parsing flags can introduce maintenance overhead. Evaluate if Cobra covers this usage or whether a custom approach is beneficial long term.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-16T14:29:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue, _ := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
path: pkg/config/config.go
user: login: github-advanced-security[bot]
body: Comment should end in a period

[Show more details](https://github.com/cloudposse/atmos/security/code-scanning/205)
created_at: 2025-03-16T14:29:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue, _ := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
+func parseFlags() (map[string]string, error) {
path: pkg/config/config.go
user: login: github-advanced-security[bot]
body: parseFlags - result 1 (error) is always nil

[Show more details](https://github.com/cloudposse/atmos/security/code-scanning/206)
created_at: 2025-03-16T14:29:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue, _ := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
+func parseFlags() (map[string]string, error) {
+	args := os.Args
+	flags := make(map[string]string)
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+
+		// Check if the argument starts with '--' (double dash)
+		if strings.HasPrefix(arg, "--") {
path: pkg/config/config.go
user: login: github-advanced-security[bot]
body: early-return: if c { ... } else { ... continue } can be simplified to if !c { ... continue } ...

[Show more details](https://github.com/cloudposse/atmos/security/code-scanning/207)
created_at: 2025-03-16T14:29:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue, _ := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
+func parseFlags() (map[string]string, error) {
+	args := os.Args
+	flags := make(map[string]string)
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+
+		// Check if the argument starts with '--' (double dash)
+		if strings.HasPrefix(arg, "--") {
+			// Strip the '--' prefix and check if it's followed by a value
+			arg = arg[2:]
+			if strings.Contains(arg, "=") {
path: pkg/config/config.go
user: login: github-advanced-security[bot]
body: ifElseChain: rewrite if-else to switch statement

[Show more details](https://github.com/cloudposse/atmos/security/code-scanning/208)
created_at: 2025-03-16T14:29:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue, _ := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
+func parseFlags() (map[string]string, error) {
+	args := os.Args
+	flags := make(map[string]string)
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+
+		// Check if the argument starts with '--' (double dash)
+		if strings.HasPrefix(arg, "--") {
+			// Strip the '--' prefix and check if it's followed by a value
+			arg = arg[2:]
+			if strings.Contains(arg, "=") {
+				// Case like --flag=value
+				parts := strings.SplitN(arg, "=", 2)
+				flags[parts[0]] = parts[1]
+			} else if i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") {
+				// Case like --flag value
+				flags[arg] = args[i+1]
+				i++ // Skip the next argument as it's the value
+			} else {
+				// Case where flag has no value, e.g., --flag (we set it to "true")
+				flags[arg] = "true"
+			}
+		} else {
+			// It's a regular argument, not a flag, so we skip
+			continue
+		}
+	}
+	return flags, nil
+}
path: pkg/config/config.go
user: login: samtholiya
body: This is old code and i just moved here. We have a better plan for future pr.
created_at: 2025-03-16T14:31:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue, _ := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
+func parseFlags() (map[string]string, error) {
+	args := os.Args
+	flags := make(map[string]string)
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+
+		// Check if the argument starts with '--' (double dash)
+		if strings.HasPrefix(arg, "--") {
+			// Strip the '--' prefix and check if it's followed by a value
+			arg = arg[2:]
+			if strings.Contains(arg, "=") {
+				// Case like --flag=value
+				parts := strings.SplitN(arg, "=", 2)
+				flags[parts[0]] = parts[1]
+			} else if i+1 < len(args) && !strings.HasPrefix(args[i+1], "--") {
+				// Case like --flag value
+				flags[arg] = args[i+1]
+				i++ // Skip the next argument as it's the value
+			} else {
+				// Case where flag has no value, e.g., --flag (we set it to "true")
+				flags[arg] = "true"
+			}
+		} else {
+			// It's a regular argument, not a flag, so we skip
+			continue
+		}
+	}
+	return flags, nil
+}
path: pkg/config/config.go
user: login: coderabbitai[bot]
body: @samtholiya Understood! Thanks for clarifying that you've just relocated existing code and have plans to address it properly in a future PR. That's a sensible approach to handling technical debt incrementally.



<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@samtholiya Understood! Thanks for clarifying that you've just relocated existing code and have plans to address it properly in a future PR. That's a sensible approach to handling technical debt incrementally.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-16T14:31:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,66 @@
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
+	if os.Getenv("ATMOS_LOGS_LEVEL") != "" {
+		atmosConfig.Logs.Level = os.Getenv("ATMOS_LOGS_LEVEL")
+	}
+	flagKeyValue := parseFlags()
+	if v, ok := flagKeyValue["logs-level"]; ok {
+		atmosConfig.Logs.Level = v
+	}
+	if os.Getenv("ATMOS_LOGS_FILE") != "" {
+		atmosConfig.Logs.File = os.Getenv("ATMOS_LOGS_FILE")
+	}
+	if v, ok := flagKeyValue["logs-file"]; ok {
+		atmosConfig.Logs.File = v
+	}
+}
+
+// TODO: This function works well, but we should generally avoid implementing manual flag parsing,
+// as Cobra typically handles this.
+
+// If there's no alternative, this approach may be necessary.
+// However, this TODO serves as a reminder to revisit and verify if a better solution exists.
+
+// Function to manually parse flags with double dash "--" like Cobra
path: pkg/config/config.go
user: login: github-advanced-security[bot]
body: Comment should end in a period

[Show more details](https://github.com/cloudposse/atmos/security/code-scanning/209)
created_at: 2025-03-16T14:35:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,64 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
path: pkg/config/config.go
user: login: osterman
body: ```suggestion
	// TODO: This is a quick patch to mitigate the issue we can look for better code later
```

Please elaborate on "the issue" that it's a quick fix for so that someone reading this would understand, without previous context.
created_at: 2025-03-17T02:03:13Z
author_association: MEMBER
diff_hunk: @@ -1 +1 @@
-ERRO Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]
+FATA Invalid log level `XTrace`. Valid options are: [Trace Debug Info Warning Off]
path: tests/snapshots/TestCLICommands_Invalid_Log_Level_in_Environment_Variable.stderr.golden
user: login: osterman
body: Good
created_at: 2025-03-17T02:04:02Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,5 @@
-DEBU Found ENV var ATMOS_LOGS_FILE=/dev/stderr
+DEBU Set logs-level=debug logs-file=/dev/stdout
path: tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
user: login: osterman
body: This concerns me that something is no going to `/dev/stdout` for logging. That's fine if we're testing it's possible, but we shoudl generally NEVER send logs to `stdout` as it breaks any/all github actions that call atmos and process JSON output.
created_at: 2025-03-17T02:09:28Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,5 @@
-DEBU Found ENV var ATMOS_LOGS_FILE=/dev/stderr
+DEBU Set logs-level=debug logs-file=/dev/stdout
path: tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
user: login: osterman
body: Note, previously it was `/dev/stderr` and now it's `/dev/stdout`
created_at: 2025-03-17T02:09:55Z
author_association: MEMBER
diff_hunk: @@ -1,7 +1,5 @@
-DEBU Found ENV var ATMOS_LOGS_FILE=/dev/stderr
+DEBU Set logs-level=debug logs-file=/dev/stdout
path: tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
user: login: samtholiya
body: Maybe you missed the line 4 😅 
![image](https://github.com/user-attachments/assets/757b1c25-3167-4ebc-870d-571062880657)

This test checks if the code first loaded env config that had `/dev/stderr` and then overrides it with flag that had `/dev/stdout`

So the output is as per expectation.
created_at: 2025-03-17T07:53:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -385,11 +386,64 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
 			atmosConfig.StackType = "Logical"
 		}
 	}
+	setLogConfig(&atmosConfig)
 
 	atmosConfig.Initialized = true
 	return atmosConfig, nil
 }
 
+func setLogConfig(atmosConfig *schema.AtmosConfiguration) {
+	// TODO: This is a quick patch to mitigate the issue we can look for better code later
path: pkg/config/config.go
user: login: samtholiya
body: Added issue link :) 
created_at: 2025-03-17T20:55:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,5 @@
-DEBU Found ENV var ATMOS_LOGS_FILE=/dev/stderr
+DEBU Set logs-level=debug logs-file=/dev/stdout
path: tests/snapshots/TestCLICommands_Valid_log_file_in_flag_should_be_priortized_over_env_and_config.stdout.golden
user: login: osterman
body: Agree, based on this test case, `/dev/stdout` is correct since that's the flag passed.

```
- name: "Valid log file in flag should be priortized over env and config"
    enabled: true
    snapshot: true
    description: "Test validation of env priority over config"
    workdir: "../"
    command: "atmos"
    args:
      - version
      - "--logs-level"
      - "Debug"
      - "--logs-file"
      - "/dev/stdout"
    env:
      ATMOS_VERSION_CHECK_ENABLED: "false"
      ATMOS_LOGS_FILE: "/dev/stderr"
    expect:
      diff:
        - '👽 Atmos (\d+\.\d+\.\d+|test) on [a-z]+/[a-z0-9]+'
      stdout:
        # Debug logs should be in stdout as we have choosen /dev/stdout in flag
        - "DEBU"
      exit_code: 0
```
created_at: 2025-03-17T21:17:12Z
author_association: MEMBER
diff_hunk: @@ -18,12 +18,11 @@ tests:
       os: !not windows
     expect:
       exit_code: 0
+      # This will output in stdout because of the way our tty simulator works.
       stdout:
         - "Fetching baz output from component-3 in nonprod"
         - "Fetching foo output from component-1 in nonprod"
         - "Fetching bar output from component-2 in nonprod"
path: tests/test-cases/atmos-functions.yaml
user: login: osterman
body: Oh wait! This is wrong. 

This test should not be passing!

https://github.com/cloudposse/atmos/pull/1103/files#diff-7aee27a48716f122e74aa8a00bbd13f49e149838fdb31506e2bd48ad147d4f1eR2-R25

We should not be outputting to `stdout`
created_at: 2025-03-17T21:19:36Z
author_association: MEMBER
diff_hunk: @@ -18,12 +18,11 @@ tests:
       os: !not windows
     expect:
       exit_code: 0
+      # This will output in stdout because of the way our tty simulator works.
       stdout:
         - "Fetching baz output from component-3 in nonprod"
         - "Fetching foo output from component-1 in nonprod"
         - "Fetching bar output from component-2 in nonprod"
path: tests/test-cases/atmos-functions.yaml
user: login: osterman
body: > This will output in stdout because of the way our tty simulator works.

Nevermind. This is why it's going to `stdout`.
created_at: 2025-03-17T21:22:16Z
author_association: MEMBER

PR comments:
user: login: osterman
created_at: 2025-03-01T13:42:05Z
body: We added this in an emergency patch release. I don’t believe the underlying issue is resolved.
author_association: MEMBER
user: login: samtholiya
created_at: 2025-03-01T14:43:22Z
body: > We added this in an emergency patch release. I don’t believe the underlying issue is resolved.

So these lines were updated because there was a code that actually assigned these values.
I wanted to check if it already made sense. And i found out it does not.

You are right to say the Patchy work is not getting refactored and that is not the intent of this pr. The intent of this pr is to clear this unwanted condition. 

Just cleaning things :) 
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-02T22:37:38Z
body: ## [Codecov](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) Report
Attention: Patch coverage is `30.00000%` with `35 lines` in your changes missing coverage. Please review.
> Project coverage is 21.64%. Comparing base [(`56a325b`)](https://app.codecov.io/gh/cloudposse/atmos/commit/56a325b9e4d034d9b0d6955c7a00585d167b0248?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) to head [(`9e70a8c`)](https://app.codecov.io/gh/cloudposse/atmos/commit/9e70a8c605ff46d3d1a9ad9f9516afe1ba632822?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) | Patch % | Lines |
|---|---|---|
| [pkg/config/config.go](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&filepath=pkg%2Fconfig%2Fconfig.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse#diff-cGtnL2NvbmZpZy9jb25maWcuZ28=) | 35.89% | [21 Missing and 4 partials :warning: ](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) |
| [internal/exec/terraform\_outputs.go](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&filepath=internal%2Fexec%2Fterraform_outputs.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse#diff-aW50ZXJuYWwvZXhlYy90ZXJyYWZvcm1fb3V0cHV0cy5nbw==) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) |
| [cmd/root.go](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&filepath=cmd%2Froot.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse#diff-Y21kL3Jvb3QuZ28=) | 25.00% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) |
| [pkg/utils/log\_utils.go](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&filepath=pkg%2Futils%2Flog_utils.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse#diff-cGtnL3V0aWxzL2xvZ191dGlscy5nbw==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) |
| [pkg/config/utils.go](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&filepath=pkg%2Fconfig%2Futils.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse#diff-cGtnL2NvbmZpZy91dGlscy5nbw==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1103      +/-   ##
==========================================
+ Coverage   21.29%   21.64%   +0.35%     
==========================================
  Files         172      172              
  Lines       19309    19292      -17     
==========================================
+ Hits         4112     4176      +64     
+ Misses      14425    14336      -89     
- Partials      772      780       +8     
```

| [Flag](https://app.codecov.io/gh/cloudposse/atmos/pull/1103/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/cloudposse/atmos/pull/1103/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse) | `21.64% <30.00%> (+0.35%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cloudposse/atmos/pull/1103?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudposse).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-06T20:12:43Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough
The pull request centralizes and refactors the logging configuration within the application. Logging settings are now derived exclusively from the `atmosConfig` structure rather than from command-line flags or environment variables. The changes include updating the logging setup in `cmd/root.go`, adding a new function to configure log levels in `pkg/config/config.go`, and removing redundant environment variable handling. Logging outputs have been standardized using a new utility function, and test snapshots have been updated to reflect changes in log messages and output destinations.

## Changes

| Files                                                                                          | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `cmd/root.go`, `pkg/config/config.go`                                                          | Modified logging setup to read log level from `atmosConfig`; added new function `setLogConfig` to configure logging; updated import of the logger package.                                                                                                                                                                                                                                                                                                                    |
| `internal/exec/terraform_outputs.go`                                                           | Unified logging output in the `GetTerraformOutput` function by removing conditional log level checks and using `u.PrintfMessageToTUI` for both error and success messages.                                                                                                                                                                                                                                                                                              |
| `pkg/config/utils.go`                                                                          | Removed processing and validation of `ATMOS_LOGS_FILE` and `ATMOS_LOGS_LEVEL` environment variables; updated logging for `ATMOS_COMPONENTS_TERRAFORM_APPEND_USER_AGENT` to use `log.Debug`.                                                                                                                                                                                                                                                                                  |
| `pkg/utils/log_utils.go`                                                                       | Added new function `PrintfMessageToTUI` for formatted message output to standard error; updated comment for `LogErrorAndExit`.                                                                                                                                                                                                                                                                                                                                                 |
| Tests (various snapshots and test cases under `tests/snapshots/` and `tests/test-cases/`)       | Updated expected log messages regarding log level, log file, and OS/architecture outputs; replaced some log prefixes (e.g., from "ERRO" to "FATA") and removed redundant messages from command line flags.                                                                                                                                                                                                                                                          |
| `cmd/terraform_generate_backend_test.go`                                                     | Replaced the call from `RootCmd.Execute()` to `Execute()` in the test function, indicating a refactoring of the command execution mechanism.                                                                                                                                                                                                                                                                                                                              |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant App
    participant AtmosConfig
    participant Logger
    participant LogUtil

    App->>AtmosConfig: Initialize configuration
    AtmosConfig-->>App: Return atmosConfig data
    App->>App: Call setLogConfig(atmosConfig)
    App->>Logger: Retrieve current log level via log package
    Logger-->>App: Return current log level
    App->>AtmosConfig: Update atmosConfig.Logs.Level in setLogConfig
    App->>Logger: Call logger.ParseLogLevel(atmosConfig.Logs.Level)
    alt Successful parsing
        Logger-->>App: Return log level value
        App->>Logger: Log debug message (logs-level and file details)
    else Error encountered
        Logger-->>App: Return parsing error
        App->>Logger: Log fatal error message and exit
    end
```

## Assessment against linked issues

| Objective                                                                                      | Addressed | Explanation |
|------------------------------------------------------------------------------------------------|-----------|-------------|
| Remove unused Flags value code (DEV-3074)                                                    | ✅        |             |

## Possibly related PRs
- **cloudposse/atmos#1029:** The changes in the main PR and the retrieved PR are related as both modify the logging setup within the `setupLogger` function in `cmd/root.go`, specifically focusing on how log output destinations are configured and handled.
- **cloudposse/atmos#1031:** The changes in the main PR focus on modifying the logging setup and configuration handling in the `setupLogger` function, while the retrieved PR also alters the logging configuration but specifically addresses the command-line flag parsing and its priority over environment variables and configuration files, indicating a direct relationship in their logging configuration logic.
- **cloudposse/atmos#930:** The changes in the main PR, which involve modifications to the logging setup and the removal of command-line flag handling for log levels, are directly related to the changes in the retrieved PR, which implements validation for log levels in the same context of logging configuration.

## Suggested reviewers
- **osterman**
- **aknysh**

</details>

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.
> 
> <details>
> <summary>🔧 golangci-lint (1.62.2)</summary>
> 
> Error: can't load config: the Go language version (go1.23) used to build golangci-lint is lower than the targeted Go version (1.24.0)
> Failed executing command with error: can't load config: the Go language version (go1.23) used to build golangci-lint is lower than the targeted Go version (1.24.0)
> 
> 
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡🧪 Multi-step agentic review comment chat (experimental)</summary>
> 
> - We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
> 						- To enable this feature, set early_access to true under in the settings.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=cloudposse/atmos&utm_content=1103):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` or `@auto-summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` or `@auto-title` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: mergify[bot]
created_at: 2025-03-13T00:17:03Z
body: 💥 This pull request now has conflicts. Could you fix it @samtholiya? 🙏
author_association: NONE
user: login: osterman
created_at: 2025-03-15T15:33:31Z
body: @samtholiya tests for stderr are failing. Something wrong with the output?
author_association: MEMBER
user: login: samtholiya
created_at: 2025-03-16T15:27:44Z
body: > @samtholiya tests for stderr are failing. Something wrong with the output?|

Fixed that. Happy to have this new test case in the mainline that helped a lot.
author_association: CONTRIBUTOR
user: login: mergify[bot]
created_at: 2025-03-17T07:54:01Z
body: 💥 This pull request now has conflicts. Could you fix it @samtholiya? 🙏
author_association: NONE