diff --git a/context.go b/context.go index 2015306..9c15fd4 100644 --- a/context.go +++ b/context.go @@ -40,6 +40,8 @@ package log import ( "context" "fmt" + "strconv" + "strings" "github.com/sirupsen/logrus" ) @@ -120,8 +122,19 @@ const ( // - "error" ([ErrorLevel]) // - "fatal" ([FatalLevel]) // - "panic" ([PanicLevel]) +// +// In addition, a numeric value can be provided using +// the level range defined by Go's slog library: +// +// - -8: trace +// - -4: debug +// - 0: info +// - 4: warn +// - 8: error +// - 10: fatal +// - 12: panic func SetLevel(level string) error { - lvl, err := logrus.ParseLevel(level) + lvl, err := parseLevel(level) if err != nil { return err } @@ -130,6 +143,50 @@ func SetLevel(level string) error { return nil } +func parseLevel(level string) (Level, error) { + switch strings.ToLower(level) { + case "trace": + return TraceLevel, nil + case "debug": + return DebugLevel, nil + case "info": + return InfoLevel, nil + case "warn", "warning": + return WarnLevel, nil + case "error": + return ErrorLevel, nil + case "fatal": + return FatalLevel, nil + case "panic": + return PanicLevel, nil + } + + // Default to parsing as numeric level + if v, err := strconv.Atoi(level); err == nil { + return numericLevel(v), nil + } + return InfoLevel, fmt.Errorf("unknown log level: %s", level) +} + +// numericLevel returns the logrus level for the given integer value, +// choosing the nearest level without going above the given value. +func numericLevel(v int) Level { + if v <= -8 { + return TraceLevel + } else if v <= -4 { + return DebugLevel + } else if v <= 0 { + return InfoLevel + } else if v <= 4 { + return WarnLevel + } else if v <= 8 { + return ErrorLevel + } else if v <= 10 { + return FatalLevel + } + return PanicLevel +} + // GetLevel returns the current log level. func GetLevel() Level { return L.Logger.GetLevel() diff --git a/context_test.go b/context_test.go index 1b6d4a6..f247728 100644 --- a/context_test.go +++ b/context_test.go @@ -38,6 +38,70 @@ func TestLoggerContext(t *testing.T) { } } +func TestSetLevel(t *testing.T) { + for _, tc := range []struct { + input string + expected Level + wantErr bool + }{ + // Named levels + {input: "trace", expected: TraceLevel}, + {input: "debug", expected: DebugLevel}, + {input: "info", expected: InfoLevel}, + {input: "warn", expected: WarnLevel}, + {input: "warning", expected: WarnLevel}, + {input: "error", expected: ErrorLevel}, + {input: "fatal", expected: FatalLevel}, + {input: "panic", expected: PanicLevel}, + {input: "INFO", expected: InfoLevel}, + {input: "DEBUG", expected: DebugLevel}, + + // Exact numeric levels + {input: "-8", expected: TraceLevel}, + {input: "-4", expected: DebugLevel}, + {input: "0", expected: InfoLevel}, + {input: "4", expected: WarnLevel}, + {input: "8", expected: ErrorLevel}, + {input: "10", expected: FatalLevel}, + {input: "12", expected: PanicLevel}, + + // Nearest numeric levels (without going above) + {input: "-10", expected: TraceLevel}, + {input: "-6", expected: DebugLevel}, + {input: "-5", expected: DebugLevel}, + {input: "-2", expected: InfoLevel}, + {input: "1", expected: WarnLevel}, + {input: "2", expected: WarnLevel}, + {input: "3", expected: WarnLevel}, + {input: "6", expected: ErrorLevel}, + {input: "7", expected: ErrorLevel}, + {input: "9", expected: FatalLevel}, + {input: "11", expected: PanicLevel}, + {input: "13", expected: PanicLevel}, + {input: "100", expected: PanicLevel}, + + // Invalid + {input: "bogus", wantErr: true}, + {input: "", wantErr: true}, + } { + t.Run(tc.input, func(t *testing.T) { + err := SetLevel(tc.input) + if tc.wantErr { + if err == nil { + t.Fatal("expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if actual := GetLevel(); actual != tc.expected { + t.Errorf("expected level %v, got %v", tc.expected, actual) + } + }) + } +} + func TestCompat(t *testing.T) { expected := Fields{ "hello1": "world1",