When you are developing a web application, it’s important to understand what’s going on behind the scenes. Logging plays a critical role in providing insights into your web server’s activities and help you identify issues, monitor performance and ensure that the application runs well. In this post, I will explain how to enable logging in a Go web application with Zap.

We will understand the basics of setting up Zap with Go and how you can use it to write logs to the standard output as well as a special log file.

If you are new to working with web servers in Go, I recommend going through my post on setting up a Go HTTP Web Server.

1 – What is Zap?

When it comes to logging in Go, Zap is a pretty formidable choice.

And it’s not without reason.

Zap is a high-performance logging library that’s designed for Go applications. With it’s lightning-fast performance and rich features, Zap has become the primary choice for logging within the Go development community.

Here are some key reasons for this popularity:

  • Performance – Zap is built with a focus on performance, making it exceptionally fast and lightweight. It basically ensures that logging doesn’t introduce undue overhead to your Go web servers.
  • Structured Logging – Zap provides the ability to log data in a structured format. This makes it easy to parse and analyze log entries.
  • Multiple Outputs – With Zap, you can also log output to various destinations such as files, standard output or even external services. In other words, you can easily tailor your logging strategy to specific needs.
  • Active Development – Zap is actively maintained and developed. This means it’s constantly evolving along with the Go ecosystem and offers cutting-edge features.

2 – Setting up Zap Logging in a Go Project

Getting started with Zap is quite straightforward.

To begin with, you need to install Zap in your Go project. To do so, open the terminal and execute the following command to fetch the Zap package.

$ go get -u go.uber.org/zap

Basically, this command will download and install Zap so that you can use it in your project.

In the next step, you need to import the Zap package into your Go application. This is done with the below import statement.

import "go.uber.org/zap"

Following this, you need to configure Zap.

It provides basic configuration options through its zap.NewProduction() and zap.NewDevelopment() functions. Basically, these functions create pre-configured logger instances that are suitable for production and development environments.

Here’s an example on how to use them.

package main

import (
	"go.uber.org/zap"
)

func main() {
	logger, err := zap.NewDevelopment()

	if err != nil {
		panic(err)
	}

	defer logger.Sync()

	logger.Info("Logging",
		zap.String("username", "john_doe"),
	)
}

If you run this program, you should see the below output in the console.

2023-10-03T08:41:21.190+0530    INFO    logging-demo/main.go:16 Logging {"username": "john_doe"}

Basically, in the snippet, we create a development logger instance using zap.NewDevelopment(). Then, we use the logger.Info() method to write a structured log to the console.

3 – Logging to File with Go and Zap

Let’s now look at another case.

Instead of the standard output console, what if we wanted to write the logs to a file?

You can easily achieve this using Zap.

Check the below example:

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {

	config := zap.NewProductionConfig()
	config.OutputPaths = []string{"logs/app.log"}
	config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder

	logger, err := config.Build()

	if err != nil {
		panic(err)
	}

	defer logger.Sync()

	logger.Info("Logging setup with Zap in Go")

	logger.Info("User Info",
		zap.String("username", "john_doe"),
		zap.Int("user_id", 567),
		zap.Bool("success", true),
	)
}

Let’s understand what’s going on here:

  • First, we configure a Zap logger with production-level settings. The log output is directed to a file named “app.log” and the log levels are formatted with capital letters for better readability.
  • Next, we initialize the logger with the specified configuration. In case of any errors during the process, it will panic and indicate a critical issue.
  • To ensure that any buffered log entries are flushed before the program exits, it defers the logger.Sync() call. This is a best practice to avoid losing log data
  • Lastly, the code uses the logger instance to log two messages. The first message just prints a basic info text. The second message provides additional structured data including a username, user ID and success boolean value.

If you run the program, you should be able to see the log entries in the “app.log” file. Just remember to create a folder named “logs” manually. The log file will be automatically created.

4 – Go Web Server Logging with Zap

Let’s now look at how you can create a middleware to perform logging in a Go web server with Zap.

Logging middleware is quite valuable for capturing information about incoming requests and outgoing responses.

Think of a middleware function as an intermediary that sits between the client’s HTTP request and the final handler function. These middleware functions can perform tasks like authentication, logging and request modification.

In this case, we’ll focus on creating a logging middleware to capture and log information about each HTTP request.

See the below code:

package main

import (
	"net/http"

	"go.uber.org/zap"
)

type ResponseWriter struct {
	http.ResponseWriter
	status int
}

func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
	return &ResponseWriter{w, http.StatusOK}
}

func (rw *ResponseWriter) WriteHeader(code int) {
	rw.status = code
	rw.ResponseWriter.WriteHeader(code)
}

func LoggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Create a logger instance
		logger, _ := zap.NewProduction()
		defer logger.Sync()

		// Log the incoming request
		logger.Info("Incoming request",
			zap.String("method", r.Method),
			zap.String("path", r.URL.Path),
		)

		// Create a response writer that captures the response status code
		rw := NewResponseWriter(w)

		// Pass the request to the next handler
		next.ServeHTTP(rw, r)
	})
}

func main() {
	// Initialize Zap logger (customize according to your configuration)
	logger, _ := zap.NewProduction()
	defer logger.Sync()

	// Define a custom handler function for your route
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Your route handler logic here
		// For example, you can log the incoming request
		logger.Info("Incoming request",
			zap.String("method", r.Method),
			zap.String("path", r.URL.Path),
		)

		// Write a response
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("Hello, World!"))

		// Log the response
		logger.Info("Outgoing response",
			zap.Int("status", http.StatusOK),
		)
	})

	// Start the server
	if err := http.ListenAndServe(":8080", nil); err != nil {
		logger.Error("Server error", zap.Error(err))
	}
}

Let’s go through the code:

  • At the every top, we have the necessary import statements.
  • The ResponseWriter struct defines a custom ResponseWriter type that embeds an http.ResponseWriter interface and includes an additional field status.
  • Next, we have the NewResponseWriter function which is a constructor function for the ResponseWriter type. It takes an http.ResponseWriter as an argument and returns a pointer to a new ResponseWriter instance with an initial status code of 200.
  • The LoggingMiddleware function takes an http.Handler as an argument and returns a new http.Handler that logs incoming requests and their responses.
  • Inside the middleware, we create a new Zap logger instance and use it to log details of the request.
  • The main function is where the server setup and request handling occur.
  • It initializes a Zap logger instance and sets up an HTTP request handler using http.HandleFunc.
  • Within the handler, it logs the incoming request, sends an HTTP response and logs the response.
  • Finally, the server listens on port 8080 and uses the custom middleware to log requests and responses.

5 – Best Practices Go Logging with Zap

Here are some best practices that you should try and follow while logging in Go with Zap.

Structured Logging

Structured logs are logs with consistent format. Basically, such logs make it easy to extract meaningful information.

Zap excels at writing logs with a structure, allowing you to log data as key-value pairs.

See the below example:

logger.Info("User login",
    zap.String("username", "john_doe"),
    zap.Int("user_id", 123),
    zap.Bool("success", true),
)

Descriptive Log Messages

Ensure that your log messages are clear and informative.

Try and include specific details about the error such as its context and any relevant data. For example:

logger.Error("Failed to process request",
    zap.String("endpoint", "/api/user"),
    zap.Error(err),
)

Include Timestamps

Logging timestamps are crucial for tracking events.

Zap automatically adds timestamps to log entries so you don’t have to worry about manually including them.

Log Levels

Also, use appropriate log levels such as Debug, Info, Warn, Error and Fatal to categorize the severity of each log entry.

Debug messages help during development and debugging while Error and Fatal messages indicate issues that need attention.

Conclusion

In Go web development, effectively logging is not merely a helpful feature but an essential tool for ensuring the reliability and maintainability of your applications.

In this post, we focused on the specific aspects of logging in Go with Zap. We also looked at logging to the console as well as a dedicated file.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.

Categories: BlogGolang

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *