Search code examples
gouber-api

How to dynamically change log level in uber/zap logger


My project structure is like following:

  • cmd
  • app
  • pkg
    • logger
    • config

In my logger package I have following peace of code which just creates a Logger and replaces zap's global logger

var logger *zap.Logger
var atomicLevel zap.AtomicLevel

func init() {
    lmb := config.NewLumberjack()
    atomicLevel = newAtomicLevel()
    logger = newLogger(lmb, atomicLevel)
    zap.ReplaceGlobals(logger)
    setRotation(lmb)
    onLogLevelChange()
}

func SetLevel(l string) {
    atomicLevel.SetLevel(config.ParseLevel(l))
}

and in my main codebase in app package where business logic is placed I should sometimes change logging level and I do it like this

logger.SetLevel("debug")
zap.L().Debug("Message", zap.Duration("exec_time", time.Second))

The problem is I don't want to call function from another package to change the behavior of an object which is located completely in different package. Are there any other better approaches to this problem?


Solution

  • I think that in the case, when you do not want to switch logger itself from another package or wrap it with high level setter, you could make a centralized switching of the logging level:

    1) Register the ServeHTTP for logger, where you should pass the AtomicLevel of your logger. This part of the docs will help in this case. Also this link could be helpful too. Here you could switch your logger level with PUT http request.

    2) Make the same as in point 1) approach, but with switching level with system signal (for example USR2). You need to place code, which wait for signals (SIGKILL, SIGTERM and USR2) in an endless loop select like this:

    for {
        select {
        case usrSig := <-WaitForOsUser2Signal():
            // here you can switch your global logger level with atomicLevel
            atomicLevel.SetLevel(zap.ErrorLevel)
        case sig := <-WaitForOsStopProcessSignals():
            // here you should handle graceful shutdown of your app
            return
        }
    }
    

    And realization of functions from select block:

    func WaitForOsStopProcessSignals() <-chan os.Signal {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    
        return sigCh
    }
    
    func WaitForOsUser2Signal() <-chan os.Signal {
        usr2Ch := make(chan os.Signal, 1)
        signal.Notify(usr2Ch, syscall.SIGUSR2)
    
        return usr2Ch
    }
    

    Hope this will help.