zap icon indicating copy to clipboard operation
zap copied to clipboard

Getting level from zap logger

Open krupyansky opened this issue 3 years ago • 3 comments

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.

krupyansky avatar Aug 05 '22 09:08 krupyansky

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?

abhinav avatar Aug 05 '22 23:08 abhinav

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 avatar Aug 08 '22 04:08 prashantv

@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 avatar Aug 08 '22 09:08 krupyansky

@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.

abhinav avatar Aug 15 '22 20:08 abhinav

v1.23.0 contains this feature - closing this as done!

sywhang avatar Aug 25 '22 16:08 sywhang