How to Use zap for Structured Logging in Go

Use Zap by configuring a logger with `zap.NewProductionConfig()` or `zap.NewDevelopmentConfig()`, then call `Sugar()` to get a simpler API or stick with the core `Logger` for maximum performance.

Use Zap by configuring a logger with zap.NewProductionConfig() or zap.NewDevelopmentConfig(), then call Sugar() to get a simpler API or stick with the core Logger for maximum performance. Structured logging is automatic; simply pass key-value pairs to methods like Info(), Error(), or Debug(), and Zap will serialize them into JSON or console formats without manual string formatting.

Here is a production-ready setup using the core API for performance-critical paths:

package main

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

func main() {
	// Configure the logger for production (JSON output, sampling, etc.)
	config := zap.NewProductionConfig()
	config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder

	logger, err := config.Build(zap.AddCallerSkip(1))
	if err != nil {
		panic(err)
	}
	defer logger.Sync()

	// Structured log entry with fields
	logger.Info("User login successful",
		zap.String("user_id", "12345"),
		zap.String("ip", "192.168.1.1"),
		zap.Duration("duration", 1500000000), // 1.5 seconds
		zap.Bool("is_new_user", true),
	)
}

For development or quick prototyping where you prefer a fmt.Printf-like syntax, use the Sugar wrapper. It accepts variadic arguments but still outputs structured data:

package main

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

func main() {
	// Create a sugared logger for convenience
	logger := zap.NewExample().Sugar()
	defer logger.Sync()

	// Structured logging with a familiar API
	logger.Infow("Processing request",
		"method", "POST",
		"path", "/api/v1/users",
		"status", 201,
	)

	// Error logging with stack trace (if enabled in config)
	logger.Errorw("Database connection failed",
		"host", "db.internal",
		"port", 5432,
		"error", "connection refused",
	)
}

Key takeaways for effective usage:

  1. Always use defer logger.Sync(): This ensures all buffered log entries are flushed to disk or stdout before the program exits, preventing data loss.
  2. Prefer the core API in hot paths: The Sugar wrapper adds a small allocation overhead. In tight loops or high-throughput services, use the typed logger.Info() method with explicit zap.Field arguments.
  3. Leverage With() for context: If you have fields common to a specific scope (like a request ID), use logger.With(zap.String("request_id", "abc")) to create a child logger. This avoids repeating the same fields in every log call within that scope.
  4. Use zap.Error() for errors: Instead of converting errors to strings manually, pass the error object directly to zap.Error(err). Zap will include the error message and, if configured, the stack trace.