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
Top comments (0)