DEV Community

Cover image for Zap: Unlock the Full Potential of Logging in Go
Leapcell
Leapcell

Posted on

1 1 1 1 1

Zap: Unlock the Full Potential of Logging in Go

Image description

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

Image description

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")
Enter fullscreen mode Exit fullscreen mode
// Example Output
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}
Enter fullscreen mode Exit fullscreen mode
// 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
Enter fullscreen mode Exit fullscreen mode
// 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}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode
// 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
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

Leapcell: The Best Serverless Platform for Golang app Hosting, Async Tasks, and Redis

Image description

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.

Image description

Explore more in the documentation!

Leapcell Twitter: https://x.com/LeapcellHQ

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay