Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Validate Atmos Log Levels #930

Merged
merged 9 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions pkg/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/charmbracelet/log"
"github.com/cloudposse/atmos/pkg/logger"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/store"
u "github.com/cloudposse/atmos/pkg/utils"
Expand Down Expand Up @@ -358,6 +359,11 @@ func processEnvVars(atmosConfig *schema.AtmosConfiguration) error {
logsLevel := os.Getenv("ATMOS_LOGS_LEVEL")
if len(logsLevel) > 0 {
u.LogTrace(*atmosConfig, 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
}

Expand Down Expand Up @@ -396,6 +402,12 @@ func checkConfig(atmosConfig schema.AtmosConfiguration) error {
return errors.New("at least one path must be provided in 'stacks.included_paths' config or ATMOS_STACKS_INCLUDED_PATHS' ENV variable")
}

if len(atmosConfig.Logs.Level) > 0 {
if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil {
return err
}
}

return nil
}

Expand Down Expand Up @@ -473,6 +485,10 @@ func processCommandLineArgs(atmosConfig *schema.AtmosConfiguration, configAndSta
u.LogTrace(*atmosConfig, fmt.Sprintf("Using command line argument '%s' as path to Atmos JSON Schema", configAndStacksInfo.AtmosManifestJsonSchema))
}
if len(configAndStacksInfo.LogsLevel) > 0 {
if _, err := logger.ParseLogLevel(configAndStacksInfo.LogsLevel); err != nil {
return err
}
// Only set the log level if validation passes
atmosConfig.Logs.Level = configAndStacksInfo.LogsLevel
u.LogTrace(*atmosConfig, fmt.Sprintf("Using command line argument '%s=%s'", LogsLevelFlag, configAndStacksInfo.LogsLevel))
}
Expand Down
56 changes: 30 additions & 26 deletions pkg/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ const (
LogLevelWarning LogLevel = "Warning"
)

// logLevelOrder defines the order of log levels from most verbose to least verbose
var logLevelOrder = map[LogLevel]int{
LogLevelTrace: 0,
LogLevelDebug: 1,
LogLevelInfo: 2,
LogLevelWarning: 3,
LogLevelOff: 4,
osterman marked this conversation as resolved.
Show resolved Hide resolved
}

type Logger struct {
LogLevel LogLevel
File string
Expand All @@ -45,18 +54,14 @@ func ParseLogLevel(logLevel string) (LogLevel, error) {
return LogLevelInfo, nil
}

switch LogLevel(logLevel) { // Convert logLevel to type LogLevel
case LogLevelTrace:
return LogLevelTrace, nil
case LogLevelDebug:
return LogLevelDebug, nil
case LogLevelInfo:
return LogLevelInfo, nil
case LogLevelWarning:
return LogLevelWarning, nil
default:
return LogLevelInfo, fmt.Errorf("invalid log level '%s'. Supported log levels are Trace, Debug, Info, Warning, Off", logLevel)
validLevels := []LogLevel{LogLevelTrace, LogLevelDebug, LogLevelInfo, LogLevelWarning, LogLevelOff}
for _, level := range validLevels {
if LogLevel(logLevel) == level {
return level, nil
}
}

return "", fmt.Errorf("Error: Invalid log level '%s'. Valid options are: %v", logLevel, validLevels)
}

func (l *Logger) log(logColor *color.Color, message string) {
Expand Down Expand Up @@ -104,7 +109,7 @@ func (l *Logger) SetLogLevel(logLevel LogLevel) error {
}

func (l *Logger) Error(err error) {
if err != nil {
if err != nil && l.LogLevel != LogLevelOff {
_, err2 := theme.Colors.Error.Fprintln(color.Error, err.Error()+"\n")
if err2 != nil {
color.Red("Error logging the error:")
Expand All @@ -115,35 +120,34 @@ func (l *Logger) Error(err error) {
}
}

// isLevelEnabled checks if a given log level should be enabled based on the logger's current level
func (l *Logger) isLevelEnabled(level LogLevel) bool {
if l.LogLevel == LogLevelOff {
return false
}
return logLevelOrder[level] >= logLevelOrder[l.LogLevel]
}

func (l *Logger) Trace(message string) {
if l.LogLevel == LogLevelTrace {
if l.isLevelEnabled(LogLevelTrace) {
l.log(theme.Colors.Info, message)
}
}

func (l *Logger) Debug(message string) {
if l.LogLevel == LogLevelTrace ||
l.LogLevel == LogLevelDebug {

if l.isLevelEnabled(LogLevelDebug) {
l.log(theme.Colors.Info, message)
}
}

func (l *Logger) Info(message string) {
if l.LogLevel == LogLevelTrace ||
l.LogLevel == LogLevelDebug ||
l.LogLevel == LogLevelInfo {

l.log(theme.Colors.Default, message)
if l.isLevelEnabled(LogLevelInfo) {
l.log(theme.Colors.Info, message)
}
}

func (l *Logger) Warning(message string) {
if l.LogLevel == LogLevelTrace ||
l.LogLevel == LogLevelDebug ||
l.LogLevel == LogLevelInfo ||
l.LogLevel == LogLevelWarning {

if l.isLevelEnabled(LogLevelWarning) {
l.log(theme.Colors.Warning, message)
}
}
2 changes: 1 addition & 1 deletion pkg/utils/log_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func LogErrorAndExit(atmosConfig schema.AtmosConfiguration, err error) {
// LogError logs errors to std.Error
func LogError(atmosConfig schema.AtmosConfiguration, err error) {
if err != nil {
_, printErr := theme.Colors.Error.Fprintln(color.Error, err.Error()+"\n")
_, printErr := theme.Colors.Error.Fprintln(color.Error, err.Error())
if printErr != nil {
theme.Colors.Error.Println("Error logging the error:")
theme.Colors.Error.Printf("%s\n", printErr)
Expand Down
8 changes: 8 additions & 0 deletions tests/fixtures/invalid-log-level/atmos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
logs:
level: XTrace
file: /dev/stdout

stacks:
base_path: stacks
included_paths:
- "**/*"
8 changes: 8 additions & 0 deletions tests/fixtures/valid-log-level/atmos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
logs:
level: Trace
file: /dev/stdout

stacks:
base_path: stacks
included_paths:
- "**/*"
80 changes: 80 additions & 0 deletions tests/test-cases/log-level-validation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
tests:
- name: "Invalid Log Level in Config File"
enabled: true
description: "Test validation of invalid log level in atmos.yaml config file"
workdir: "fixtures/invalid-log-level"
command: "atmos"
args:
- terraform
- plan
- test
- -s
- test
expect:
exit_code: 1
stderr:
- "Error: Invalid log level 'XTrace'. Valid options are: \\[Trace Debug Info Warning Off\\]"

- name: "Invalid Log Level in Environment Variable"
enabled: true
description: "Test validation of invalid log level in ATMOS_LOGS_LEVEL env var"
workdir: "../"
command: "atmos"
args:
- terraform
- plan
- test
- -s
- test
env:
ATMOS_LOGS_LEVEL: XTrace
expect:
exit_code: 1
stderr:
- "Error: Invalid log level 'XTrace'. Valid options are: \\[Trace Debug Info Warning Off\\]"

- name: "Valid Log Level in Config File"
enabled: true
description: "Test validation of valid log level in atmos.yaml config file"
workdir: "fixtures/valid-log-level"
command: "atmos"
args:
- terraform
- plan
- test
- -s
- test
expect:
exit_code: 0

- name: "Valid Log Level in Environment Variable"
enabled: true
description: "Test validation of valid log level in ATMOS_LOGS_LEVEL env var"
workdir: "../"
command: "atmos"
args:
- terraform
- plan
- test
- -s
- test
env:
ATMOS_LOGS_LEVEL: Debug
expect:
exit_code: 0

- name: "Valid Log Level in Command Line Flag"
enabled: true
description: "Test validation of valid log level in --logs-level flag"
workdir: "../"
command: "atmos"
args:
- --logs-level
- Info
- terraform
- plan
- test
- -s
- test
expect:
exit_code: 0
Loading