diff --git a/pkg/cli/server/root.go b/pkg/cli/server/root.go index e335c9a4..8c2c9e42 100644 --- a/pkg/cli/server/root.go +++ b/pkg/cli/server/root.go @@ -1068,6 +1068,13 @@ func LoadConfiguration(config *config.Config, configPath string) error { return err } + // Validate log level before creating logger to avoid panic + if _, err := zlog.ParseLevel(config.Log.Level); err != nil { + logger.Error().Err(zerr.ErrBadConfig).Str("level", config.Log.Level).Msg(err.Error()) + + return err + } + log := zlog.NewLogger(config.Log.Level, config.Log.Output) if len(metaData.Keys) == 0 { diff --git a/pkg/cli/server/root_test.go b/pkg/cli/server/root_test.go index c0946917..e5b34ca5 100644 --- a/pkg/cli/server/root_test.go +++ b/pkg/cli/server/root_test.go @@ -201,6 +201,30 @@ storage: So(err, ShouldBeNil) }) + Convey("Test verify config with invalid log level", t, func(c C) { + content := `{"distSpecVersion":"1.1.1","storage":{"rootDirectory":"/tmp/zot"}, + "http":{"address":"127.0.0.1","port":"8080","realm":"zot"}, + "log":{"level":"invalid"}}` + tmpfile := MakeTempFileWithContent(t, "zot-test.json", content) + + os.Args = []string{"cli_test", "verify", tmpfile} + err := cli.NewServerRootCmd().Execute() + So(err, ShouldNotBeNil) + So(err.Error(), ShouldContainSubstring, "invalid log level") + So(err.Error(), ShouldContainSubstring, "invalid") + }) + + Convey("Test verify config with valid trace log level", t, func(c C) { + content := `{"distSpecVersion":"1.1.1","storage":{"rootDirectory":"/tmp/zot"}, + "http":{"address":"127.0.0.1","port":"8080","realm":"zot"}, + "log":{"level":"trace"}}` + tmpfile := MakeTempFileWithContent(t, "zot-test.json", content) + + os.Args = []string{"cli_test", "verify", tmpfile} + err := cli.NewServerRootCmd().Execute() + So(err, ShouldBeNil) + }) + Convey("Test verify CVE warn for remote storage", t, func(c C) { content := `{ "storage":{ diff --git a/pkg/log/log.go b/pkg/log/log.go index d97d82f1..d64dbf19 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -258,19 +258,22 @@ func (e *Event) Msg(msg string) { } } -// parseLevel converts string level to slog.Level. -func parseLevel(level string) (slog.Level, error) { +// ParseLevel converts string level to slog.Level. +func ParseLevel(level string) (slog.Level, error) { + const supportedLevels = "debug, trace, info, warn, warning, error, fatal, panic" + switch strings.ToLower(level) { - case "debug": + case "debug", "trace": return slog.LevelDebug, nil case "info": return slog.LevelInfo, nil case "warn", "warning": return slog.LevelWarn, nil - case "error": + case "error", "fatal", "panic": return slog.LevelError, nil default: - return slog.LevelInfo, errors.ErrBadConfig + return slog.LevelInfo, fmt.Errorf("%w: invalid log level '%s', supported levels are: %s", + errors.ErrBadConfig, level, supportedLevels) } } @@ -292,7 +295,7 @@ func NewLogger(level, output string) Logger { func NewAuditLogger(level, output string) *Logger { // Parse log level - lvl, err := parseLevel(level) + lvl, err := ParseLevel(level) if err != nil { panic(err) } @@ -343,7 +346,7 @@ func defaultJSONHandler(lvl slog.Leveler, writer io.Writer) *slog.JSONHandler { func NewLoggerWithWriter(level string, writer io.Writer) Logger { // Parse log level - lvl, err := parseLevel(level) + lvl, err := ParseLevel(level) if err != nil { panic(err) }