zap
zap copied to clipboard
Getting level from zap logger
How can I get log level from zap logger?
For logrus, this problem is solved in the following way: https://pkg.go.dev/github.com/sirupsen/logrus#Logger.GetLevel https://pkg.go.dev/github.com/sirupsen/logrus#Entry
I need a similar feature from zap logger.
Hey, @krupyansky, Zap does not currently have a way of accessing the log level post-construction. This is a problem we should solve, and we're going to try to think of a way around that, but meanwhile, there may be a workaround.
Workaround:
Construct your logger by supplying a zap.AtomicLevel.
If you're using zap.NewProduction or zap.NewDevelopment, you can do this:
atomLevel := zap.NewAtomicLevelAt(zap.InfoLevel)
encoderCfg := zap.NewProductionEncoderConfig()
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(encoderCfg),
zapcore.Lock(os.Stdout),
atomLevel,
))
Following that, your atomLevel will always report the current log level.
fmt.Println(atomLevel.Level())
// atomLevel will report the logger's current level.
If you're using zap.Config to load your configuration, you can already access the AtomicLevel it uses for the logger at Config.Level.
var cfg zap.Config
// ...
logger, err := cfg.Build()
// ...
atomLevel := cfg.Level
fmt.Println(atomLevel.Level())
// atomLevel will report the logger's current level.
Solution
To actually solve this problem in Zap, we need to expose a means of reading the current log level. One option is something similar to what you proposed in #1143, @krupyansky, but in a backwards compatible manner.
What if the Level() Level method was an optional upcast of zapcore.Core?
We can add a zapcore.LevelOf function that reports the level of a core if it implements that method.
Something roughly like,
package zapcore
// LevelOf reports the minimum enabled log level in the given Core.
// If the Core implements a "Level() Level" method, LevelOf delegates to it.
func LevelOf(core Core) Level {
leveler, ok := core.(interface{ Level() Level })
if ok { return leveler.Level() }
// ...
}
If the Core does not implement the method, LevelOf can make a reasonable guess of the Level by looping through all levels in-order and calling Core.Enabled on them.
We can also add a Logger.Level() method based on this function to zap.Logger and zap.SugaredLogger.
What do you think, @mway @sywhang @prashantv?
That seems reasonable to me. Looping over Enabled seems ok; though the interface technically allows something like Enabled(Info) && !Enabled(Warn). That said, I think returning the first enabled is a reasonable guess.
@prashantv
Now, if I want to get level from zap, I need use this code:
func GetLevel(core zapcore.Core) zapcore.Level {
if core.Enabled(zapcore.DebugLevel) {
return zapcore.DebugLevel
}
if core.Enabled(zapcore.InfoLevel) {
return zapcore.InfoLevel
}
if core.Enabled(zapcore.WarnLevel) {
return zapcore.WarnLevel
}
if core.Enabled(zapcore.ErrorLevel) {
return zapcore.ErrorLevel
}
if core.Enabled(zapcore.DPanicLevel) {
return zapcore.DPanicLevel
}
if core.Enabled(zapcore.PanicLevel) {
return zapcore.PanicLevel
}
if core.Enabled(zapcore.FatalLevel) {
return zapcore.FatalLevel
}
return zapcore.DebugLevel
}
@abhinav
This construction
leveler, ok := core.(interface{ Level() Level })
if ok { return leveler.Level() }
not help me.
Inheritance is not work for the case.
@krupyansky
The suggestion was not to use the core.(interface{ Level() Level }) as-is, but as part of a new LevelOf function in Zap.
The way it would work is:
- if the Core implements that interface, use it
- Otherwise loop through the levels like in your code sample
- All Core implementations shipped in Zap will implement the method
This way, when possible, we'll use the Level() method, but otherwise we'll fall back to trying every level.
I've created #1147 implementing this.
v1.23.0 contains this feature - closing this as done!