Zap: Unlock the Full Potential of Logging in Go
Daniel Hayes
Full-Stack Engineer · Leapcell
Abstract
Zap is a very fast, structured, and log-leveled Go logging library developed by Uber. According to the Uber - go Zap documentation, it performs better than similar structured logging packages and is also faster than the standard library. Specific performance tests can be found on GitHub.
GitHub address: https://github.com/uber - go/zap
Creating an Instance
Create a Logger by calling zap.NewProduction()/zap.NewDevelopment() or zap.Example(). The difference among these three methods lies in the information they will record, and the parameters can only be of the string type.
// Code var log *zap.Logger log = zap.NewExample() log, _ := zap.NewDevelopment() log, _ := zap.NewProduction() log.Debug("This is a DEBUG message") log.Info("This is an INFO message")
// Example Output {"level":"debug","msg":"This is a DEBUG message"} {"level":"info","msg":"This is an INFO message"}
// Development Output 2025-01-28T00:00:00.000+0800 DEBUG development/main.go:7 This is a DEBUG message 2025-01-28T00:00:00.000+0800 INFO development/main.go:8 This is an INFO message
// Production Output {"level":"info","ts":1737907200.0000000,"caller":"production/main.go:8","msg":"This is an INFO message"} {"level":"info","ts":1737907200.0000000,"caller":"production/main.go:9","msg":"This is an INFO message with fields","region":["us-west"],"id":2}
Comparison of the Three Creation Methods:
- Both Example and Production use JSON format for output, while Development uses a line - by - line output form.
- Development
- Prints from the warning level upwards to the stack for tracking.
- Always prints the package/file/line (method).
- Adds any additional fields at the end of the line as a JSON string.
- Prints the level name in uppercase.
- Prints the timestamp in ISO8601 format in milliseconds.
- Production
- Debug - level messages are not logged.
- For Error and Dpanic - level records, the file will be tracked in the stack, but Warn will not.
- Always adds the caller to the file.
- Prints the date in timestamp format.
- Prints the level name in lowercase.
Formatted Output
Zap has two types, *zap.Logger and *zap.SugaredLogger. The only difference between them is that we can obtain a SugaredLogger by calling the.Sugar() method of the main logger, and then use the SugaredLogger to record statements in printf format, for example:
var sugarLogger *zap.SugaredLogger func InitLogger() { logger, _ := zap.NewProduction() sugarLogger = logger.Sugar() } func main() { InitLogger() defer sugarLogger.Sync() sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err) }
Writing to a File
By default, logs are printed to the console interface of the application. However, for easy querying, logs can be written to a file. But we can no longer use the three methods for creating instances mentioned before. Instead, we use zap.New().
package main import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "os" ) var log *zap.Logger func main() { writeSyncer, _ := os.Create("./info.log") // Log file storage directory encoderConfig := zap.NewProductionEncoderConfig() // Specify time format encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoder := zapcore.NewConsoleEncoder(encoderConfig) // Get the encoder, NewJSONEncoder() outputs in JSON format, NewConsoleEncoder() outputs in plain text format core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) // The third and subsequent parameters are the log levels for writing to the file. In ErrorLevel mode, only error - level logs are recorded. log = zap.New(core,zap.AddCaller()) // AddCaller() is used to display the file name and line number. log.Info("hello world") log.Error("hello world") }
// Log file output result: 2025-01-28T00:00:00.000+0800 INFO geth/main.go:18 hello world 2025-01-28T00:00:00.000+0800 ERROR geth/main.go:19 hello world
Output to Both Console and File
If you need to output to both the console and the file, you only need to modify zapcore.NewCore. Example:
package main import ( "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "os" ) var log *zap.Logger func main() { // Get the encoder, NewJSONEncoder() outputs in JSON format, NewConsoleEncoder() outputs in plain text format encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Specify time format encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoder := zapcore.NewConsoleEncoder(encoderConfig) // File writeSyncer fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ Filename: "./info.log", // Log file storage directory MaxSize: 1, // File size limit, unit MB MaxBackups: 5, // Maximum number of retained log files MaxAge: 30, // Number of days to retain log files Compress: false, // Whether to compress }) fileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer,zapcore.AddSync(os.Stdout)), zapcore.DebugLevel) // The third and subsequent parameters are the log levels for writing to the file. In ErrorLevel mode, only error - level logs are recorded. log = zap.New(fileCore, zap.AddCaller()) // AddCaller() is used to display the file name and line number. log.Info("hello world") log.Error("hello world") }
File Splitting
Log files will grow larger over time. To avoid filling up the hard disk space, log files need to be split according to certain conditions. The Zap package itself does not provide file - splitting functionality, but it can be handled using the lumberjack package recommended by Zap.
// File writeSyncer fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ Filename: "./info.log", // Log file storage directory. If the folder does not exist, it will be created automatically. MaxSize: 1, // File size limit, unit MB MaxBackups: 5, // Maximum number of retained log files MaxAge: 30, // Number of days to retain log files Compress: false, // Whether to compress })
Writing to Files by Level
For the convenience of management personnel's querying, generally, we need to put logs below the error level into info.log, and logs at the error level and above into the error.log file. We only need to modify the third parameter of the zapcore.NewCore method, and then split the file WriteSyncer into info and error. Example:
package main import ( "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "os" ) var log *zap.Logger func main() { var coreArr []zapcore.Core // Get the encoder encoderConfig := zap.NewProductionEncoderConfig() // NewJSONEncoder() outputs in JSON format, NewConsoleEncoder() outputs in plain text format encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Specify time format encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // Display different colors according to levels. If not needed, use zapcore.CapitalLevelEncoder. //encoderConfig.EncodeCaller = zapcore.FullCallerEncoder // Display the full file path encoder := zapcore.NewConsoleEncoder(encoderConfig) // Log levels highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool{ // Error level return lev >= zap.ErrorLevel }) lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { // Info and debug levels, debug level is the lowest return lev < zap.ErrorLevel && lev >= zap.DebugLevel }) // Info file writeSyncer infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ Filename: "./log/info.log", // Log file storage directory. If the folder does not exist, it will be created automatically. MaxSize: 1, // File size limit, unit MB MaxBackups: 5, // Maximum number of retained log files MaxAge: 30, // Number of days to retain log files Compress: false, // Whether to compress }) infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer,zapcore.AddSync(os.Stdout)), lowPriority) // The third and subsequent parameters are the log levels for writing to the file. In ErrorLevel mode, only error - level logs are recorded. // Error file writeSyncer errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ Filename: "./log/error.log", // Log file storage directory MaxSize: 1, // File size limit, unit MB MaxBackups: 5, // Maximum number of retained log files MaxAge: 30, // Number of days to retain log files Compress: false, // Whether to compress }) errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer,zapcore.AddSync(os.Stdout)), highPriority) // The third and subsequent parameters are the log levels for writing to the file. In ErrorLevel mode, only error - level logs are recorded. coreArr = append(coreArr, infoFileCore) coreArr = append(coreArr, errorFileCore) log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) // zap.AddCaller() is used to display the file name and line number and can be omitted. log.Info("hello info") log.Debug("hello debug") log.Error("hello error") }
After such modification, info and debug - level logs are stored in info.log, and error - level logs are stored separately in the error.log file.
Displaying Colors in the Console by Level
Just specify the EncodeLevel of the encoder.
// Get the encoder encoderConfig := zap.NewProductionEncoderConfig() // NewJSONEncoder() outputs in JSON format, NewConsoleEncoder() outputs in plain text format encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Specify time format encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // Display different colors according to levels. If not needed, use zapcore.CapitalLevelEncoder. encoder := zapcore.NewConsoleEncoder(encoderConfig)
Displaying File Path and Line Number
As mentioned before, to display the file path and line number, just add the parameter zap.AddCaller() to the zap.New method. If you want to display the full path, you need to specify it in the encoder configuration.
// Get the encoder encoderConfig := zap.NewProductionEncoderConfig() // NewJSONEncoder() outputs in JSON format, NewConsoleEncoder() outputs in plain text format encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Specify time format encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // Display different colors according to levels. If not needed, use zapcore.CapitalLevelEncoder. encoderConfig.EncodeCaller = zapcore.FullCallerEncoder // Display the full file path encoder := zapcore.NewConsoleEncoder(encoderConfig)
Complete Code
package main import ( "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "os" ) var log *zap.Logger func main() { var coreArr []zapcore.Core // Get the encoder encoderConfig := zap.NewProductionEncoderConfig() // NewJSONEncoder() outputs in JSON format, NewConsoleEncoder() outputs in plain text format encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // Specify time format encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // Display different colors according to levels. If not needed, use zapcore.CapitalLevelEncoder. //encoderConfig.EncodeCaller = zapcore.FullCallerEncoder // Display the full file path encoder := zapcore.NewConsoleEncoder(encoderConfig) // Log levels highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool{ // Error level return lev >= zap.ErrorLevel }) lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { // Info and debug levels, debug level is the lowest return lev < zap.ErrorLevel && lev >= zap.DebugLevel }) // Info file writeSyncer infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ Filename: "./log/info.log", // Log file storage directory. If the folder does not exist, it will be created automatically. MaxSize: 2, // File size limit, unit MB MaxBackups: 100, // Maximum number of retained log files MaxAge: 30, // Number of days to retain log files Compress: false, // Whether to compress }) infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer,zapcore.AddSync(os.Stdout)), lowPriority) // The third and subsequent parameters are the log levels for writing to the file. In ErrorLevel mode, only error - level logs are recorded. // Error file writeSyncer errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ Filename: "./log/error.log", // Log file storage directory MaxSize: 1, // File size limit, unit MB MaxBackups: 5, // Maximum number of retained log files MaxAge: 30, // Number of days to retain log files Compress: false, // Whether to compress }) errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer,zapcore.AddSync(os.Stdout)), highPriority) // The third and subsequent parameters are the log levels for writing to the file. In ErrorLevel mode, only error - level logs are recorded. coreArr = append(coreArr, infoFileCore) coreArr = append(coreArr, errorFileCore) log = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) // zap.AddCaller() is used to display the file name and line number and can be omitted. log.Info("hello info") log.Debug("hello debug") log.Error("hello error") }
Leapcell: The Best Serverless Platform for Golang app Hosting, Async Tasks, and Redis
Finally, I would like to recommend the best platform for deploying Golang services: Leapcell
1. Multi - Language Support
- Develop with JavaScript, Python, Go, or Rust.
2. Deploy unlimited projects for free
- Pay only for usage — no requests, no charges.
3. Unbeatable Cost Efficiency
- Pay - as - you - go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
4. Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real - time metrics and logging for actionable insights.
5. Effortless Scalability and High Performance
- Auto - scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the documentation!
Leapcell Twitter: https://x.com/LeapcellHQ