I'm trying to migrate my application from the beautiful Logrus (very helpful for debug) and introducing the Uber log framework Zap.
With Logrus, I can initialize the logger only once and reuse it from other Go file, an example:
package main
import(
// Print filename on log
filename "github.com/onrik/logrus/filename"
// Very nice log library
log "github.com/sirupsen/logrus"
)
func main(){
// ==== SET LOGGING
Formatter := new(log.TextFormatter)
Formatter.TimestampFormat = "Jan _2 15:04:05.000000000"
Formatter.FullTimestamp = true
Formatter.ForceColors = true
log.AddHook(filename.NewHook()) // Print filename + line at every log
log.SetFormatter(Formatter)
}
From other Go file, I'm able to reuse that logger without any other initialization:
// VerifyCommandLineInput is delegated to manage the inputer parameter provide with the input flag from command line
func VerifyCommandLineInput() datastructures.Configuration {
log.Debug("VerifyCommandLineInput | Init a new configuration from the conf file")
c := flag.String("config", "./conf/test.json", "Specify the configuration file.")
flag.Parse()
if strings.Compare(*c, "") == 0 {
log.Fatal("VerifyCommandLineInput | Call the tool using --config conf/config.json")
}
file, err := os.Open(*c)
if err != nil {
log.Fatal("VerifyCommandLineInput | can't open config file: ", err)
}
defer file.Close()
decoder := json.NewDecoder(file)
cfg := datastructures.Configuration{}
err = decoder.Decode(&cfg)
if err != nil {
log.Fatal("VerifyCommandLineInput | can't decode config JSON: ", err)
}
log.Debug("VerifyCommandLineInput | Conf loaded -> ", cfg)
return cfg
}
My question is: using the Zap log framework, how can I initialize the log in the main function and use that logger from the other Go file?
You can set up your logger in the main function and call zap.ReplaceGlobals
to use it as a default global logger.
ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a function to restore the original values. It's safe for concurrent use.
Replacing the default go Global logger with zaps' implementation is possible, but discouraged.
Per their FAQ
Why include package-global loggers? Since so many other logging packages include a global logger, many applications aren't designed to accept loggers as explicit parameters. Changing function signatures is often a breaking change, so zap includes global loggers to simplify migration.
Avoid them where possible.
Depending on your needs, you can create a logger in main and pass it around or create a new logger in each package. I elected to create one in main and pass it around since I'm using an Atomic logger, which allows me to change log levels while my app is running via an API call. With a long history of using DI and consolidating code in general it does feel like code smell, but apparently it's significantly more performant for how zap works to pass it around over a singleton or global.
I suppose it's a good idea to avoid replacing the default go logger, as was mentioned by @ammills01. My approach is to create a separate package for zap logger’s configuration and initialization. Also it provides wrapper functions implementation which can be useful a lot in case of changing zap to another logging tool.
package logger
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var zapLog *zap.Logger
func init() {
var err error
config := zap.NewProductionConfig()
enccoderConfig := zap.NewProductionEncoderConfig()
zapcore.TimeEncoderOfLayout("Jan _2 15:04:05.000000000")
enccoderConfig.StacktraceKey = "" // to hide stacktrace info
config.EncoderConfig = enccoderConfig
zapLog, err = config.Build(zap.AddCallerSkip(1))
if err != nil {
panic(err)
}
}
func Info(message string, fields ...zap.Field) {
zapLog.Info(message, fields...)
}
func Debug(message string, fields ...zap.Field) {
zapLog.Debug(message, fields...)
}
func Error(message string, fields ...zap.Field) {
zapLog.Error(message, fields...)
}
func Fatal(message string, fields ...zap.Field) {
zapLog.Fatal(message, fields...)
}
So any other file in your project will be as follows
import "github.com/[your_repo_path_here]/logger"
func SomeFunc() datastructures.Configuration {
logger.Debug("VerifyCommandLineInput | Init a new configuration from the conf file")
c := flag.String("config", "./conf/test.json", "Specify the configuration file.")
flag.Parse()if strings.Compare(*c, "") == 0 {
logger.Fatal("VerifyCommandLineInput | Call the tool using --config conf/config.json")
}
file, err := os.Open(*c)
if err != nil {
logger.Fatal("VerifyCommandLineInput | can't open config file: ", err)
}
defer file.Close()
decoder := json.NewDecoder(file)
cfg := datastructures.Configuration{}
err = decoder.Decode(&cfg)
if err != nil {
logger.Fatal("VerifyCommandLineInput | can't decode config JSON: ", err)
}
logger.Debug("VerifyCommandLineInput | Conf loaded -> ", cfg)
return cfg
}
Answer by @alessiosavi originally posted into the question.
Initialize a new log and set as global as pointed by @Mikhail.
package main
import(
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func initZapLog() *zap.Logger {
config := zap.NewDevelopmentConfig()
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, _ := config.Build()
return logger
}
func main() {
loggerMgr := initZapLog()
zap.ReplaceGlobals(loggerMgr)
defer loggerMgr.Sync() // flushes buffer, if any
logger := loggerMgr.Sugar()
logger.Debug("START!")
db2.GetToken(`alessio`, `savi`, `pass`)
datastructure.LoadConfiguration()
}
Than you can use the logger in the other Go file:
func GetToken(url, user, pass string) string {
var User datastructure.User
var data string
var jsonData []byte
User.User = user
User.Pass = pass
jsonData, err := json.Marshal(User)
if err != nil {
zap.S().Errorw("Error during marshalling...", err)
return ""
}
data = string(jsonData)
zap.S().Info("Data encoded => ", data)
return ""
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With