Skip to content

Commit e5a8432

Browse files
authored
feat: add GetLogLevel and GetAllLogLevels (#160)
* feat: add GetLogLevel and GetAllLogLevels * fix: track global log level separately * fix: use zap level string conversion * test: use zapcore.Level().String() to get level string
1 parent dbee19f commit e5a8432

File tree

3 files changed

+180
-0
lines changed

3 files changed

+180
-0
lines changed

levels.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,42 @@ func LevelFromString(level string) (LogLevel, error) {
2828
err := lvl.Set(level)
2929
return LogLevel(lvl), err
3030
}
31+
32+
// LevelName returns the name of a LogLevel.
33+
func LevelName(level LogLevel) string {
34+
return zapcore.Level(level).String()
35+
}
36+
37+
// GetLogLevel returns the current log level for a given subsystem as a string.
38+
// Passing name="*" or name="" returns the defaultLevel.
39+
func GetLogLevel(name string) (string, error) {
40+
if name == "*" || name == "" {
41+
loggerMutex.RLock()
42+
defLvl := defaultLevel
43+
loggerMutex.RUnlock()
44+
return LevelName(defLvl), nil
45+
}
46+
if lvl, ok := levels[name]; ok {
47+
return lvl.Level().String(), nil
48+
}
49+
return "", ErrNoSuchLogger
50+
}
51+
52+
// GetAllLogLevels returns a map of all current log levels for all subsystems as strings.
53+
// The map includes a special "*" key that represents the defaultLevel.
54+
func GetAllLogLevels() map[string]string {
55+
result := make(map[string]string, len(levels)+1)
56+
57+
// Add the default level with "*" key
58+
loggerMutex.RLock()
59+
defLvl := defaultLevel
60+
loggerMutex.RUnlock()
61+
result["*"] = LevelName(defLvl)
62+
63+
// Add all subsystem levels
64+
for name, level := range levels {
65+
result[name] = level.Level().String()
66+
}
67+
68+
return result
69+
}

log_level_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ import (
88

99
func TestLogLevel(t *testing.T) {
1010
const subsystem = "log-level-test"
11+
12+
// Save original config and restore after test
13+
originalConfig := GetConfig()
14+
defer SetupLogging(originalConfig)
15+
16+
// Reset to a known state with error level default
17+
SetupLogging(Config{
18+
Level: LevelError,
19+
Stderr: true,
20+
})
21+
1122
logger := Logger(subsystem)
1223
reader := NewPipeReader()
1324
done := make(chan struct{})
@@ -51,3 +62,131 @@ func TestLogLevel(t *testing.T) {
5162
}
5263
<-done
5364
}
65+
66+
// Helper function to clear logger state between tests
67+
func clearLoggerState() {
68+
clear(loggers)
69+
clear(levels)
70+
}
71+
72+
func TestGetDefaultLevel(t *testing.T) {
73+
originalConfig := GetConfig()
74+
defer SetupLogging(originalConfig)
75+
76+
// Clear any state from previous tests first
77+
clearLoggerState()
78+
79+
testCases := []LogLevel{LevelDebug, LevelInfo, LevelWarn, LevelError}
80+
81+
for _, expected := range testCases {
82+
SetupLogging(Config{Level: expected, Stderr: true})
83+
84+
// empty string arg
85+
lvl, err := GetLogLevel("")
86+
if err != nil {
87+
t.Errorf("GetLogLevel() returned error: %v", err)
88+
} else if lvl != LevelName(expected) {
89+
t.Errorf("GetLogLevel() = %v, want %v", lvl, LevelName(expected))
90+
}
91+
92+
// explicit "*"
93+
lvl, err = GetLogLevel("*")
94+
if err != nil {
95+
t.Errorf(`GetLogLevel("*") returned error: %v`, err)
96+
} else if lvl != LevelName(expected) {
97+
t.Errorf(`GetLogLevel("*") = %v, want %v`, lvl, LevelName(expected))
98+
}
99+
100+
// empty string
101+
lvl, err = GetLogLevel("")
102+
if err != nil {
103+
t.Errorf(`GetLogLevel("") returned error: %v`, err)
104+
} else if lvl != LevelName(expected) {
105+
t.Errorf(`GetLogLevel("") = %v, want %v`, lvl, LevelName(expected))
106+
}
107+
}
108+
}
109+
110+
func TestGetAllLogLevels(t *testing.T) {
111+
originalConfig := GetConfig()
112+
defer SetupLogging(originalConfig)
113+
114+
// Clear any state from previous tests first
115+
clearLoggerState()
116+
117+
SetupLogging(Config{Level: LevelWarn, Stderr: true})
118+
base := GetAllLogLevels()
119+
120+
if len(base) != 1 {
121+
t.Errorf("baseline GetAllLogLevels() length = %d; want 1", len(base))
122+
}
123+
if base["*"] != LevelName(LevelWarn) {
124+
t.Errorf("baseline GetAllLogLevels()[\"*\"] = %v; want %v", base["*"], LevelName(LevelWarn))
125+
}
126+
127+
expected := map[string]LogLevel{
128+
"test1": LevelDebug,
129+
"test2": LevelInfo,
130+
"test3": LevelWarn,
131+
}
132+
SetupLogging(Config{
133+
Level: LevelError,
134+
SubsystemLevels: expected,
135+
Stderr: true,
136+
})
137+
138+
all := GetAllLogLevels()
139+
140+
if all["*"] != LevelName(LevelError) {
141+
t.Errorf(`GetAllLogLevels()["*"] = %v; want %v`, all["*"], LevelName(LevelError))
142+
}
143+
for name, want := range expected {
144+
got, ok := all[name]
145+
if !ok {
146+
t.Errorf("missing key %q in GetAllLogLevels()", name)
147+
continue
148+
}
149+
if got != LevelName(want) {
150+
t.Errorf(`GetAllLogLevels()["%s"] = %v; want %v`, name, got, LevelName(want))
151+
}
152+
}
153+
154+
// dynamic logger test
155+
_ = Logger("dynamic")
156+
if err := SetLogLevel("dynamic", "fatal"); err != nil {
157+
t.Fatalf("SetLogLevel(dynamic) failed: %v", err)
158+
}
159+
160+
all = GetAllLogLevels()
161+
if lvl, ok := all["dynamic"]; !ok {
162+
t.Error(`missing "dynamic" key after creation`)
163+
} else if lvl != LevelName(LevelFatal) {
164+
t.Errorf(`GetAllLogLevels()["dynamic"] = %v; want %v`, lvl, LevelName(LevelFatal))
165+
}
166+
167+
// ensure immutability
168+
snapshot := GetAllLogLevels()
169+
snapshot["*"] = LevelName(LevelDebug)
170+
snapshot["newkey"] = LevelName(LevelInfo)
171+
172+
// ensure original state unchanged
173+
fresh := GetAllLogLevels()
174+
if fresh["*"] != LevelName(LevelError) {
175+
t.Errorf(`immutable check failed: fresh["*"] = %v; want %v`, fresh["*"], LevelName(LevelError))
176+
}
177+
if _, exists := fresh["newkey"]; exists {
178+
t.Error(`immutable check failed: "newkey" should not leak into real map`)
179+
}
180+
}
181+
182+
func TestLevelName(t *testing.T) {
183+
testLevels := []LogLevel{LevelDebug, LevelInfo, LevelWarn, LevelError}
184+
expectNames := []string{"debug", "info", "warn", "error"}
185+
186+
for i := range testLevels {
187+
name := LevelName(testLevels[i])
188+
if name != expectNames[i] {
189+
t.Errorf("unexpected name for level: expected %s, got %s", expectNames[i], name)
190+
}
191+
}
192+
}

setup.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ var levels = make(map[string]zap.AtomicLevel)
8686
var primaryFormat LogFormat = ColorizedOutput
8787

8888
// defaultLevel is the default log level
89+
// New loggers will be set to `defaultLevel` when created
8990
var defaultLevel LogLevel = LevelError
9091

9192
// primaryCore is the primary logging core
@@ -205,6 +206,7 @@ func SetLogLevel(name, level string) error {
205206
// wildcard, change all
206207
if name == "*" {
207208
SetAllLoggers(lvl)
209+
defaultLevel = lvl
208210
return nil
209211
}
210212

0 commit comments

Comments
 (0)