Zap provides several integration packages for connecting with other systems and frameworks: gRPC logging, IO wrapping, and experimental features including slog support.
Import: import "go.uber.org/zap/zapgrpc"
Adapter for using zap with gRPC's logging interface (grpclog.LoggerV2).
func NewLogger(l *zap.Logger, options ...Option) *LoggerCreate a grpclog.LoggerV2-compatible logger backed by a zap logger.
Parameters:
l: Zap logger to wrapoptions: Configuration optionstype Logger struct {
// unexported fields
}Implements grpclog.LoggerV2 and grpclog.Logger interfaces.
func (l *Logger) Info(args ...interface{})
func (l *Logger) Infoln(args ...interface{})
func (l *Logger) Infof(format string, args ...interface{})
func (l *Logger) Warning(args ...interface{})
func (l *Logger) Warningln(args ...interface{})
func (l *Logger) Warningf(format string, args ...interface{})
func (l *Logger) Error(args ...interface{})
func (l *Logger) Errorln(args ...interface{})
func (l *Logger) Errorf(format string, args ...interface{})
func (l *Logger) Fatal(args ...interface{})
func (l *Logger) Fatalln(args ...interface{})
func (l *Logger) Fatalf(format string, args ...interface{})
func (l *Logger) V(level int) boolfunc (l *Logger) Print(args ...interface{})
func (l *Logger) Printf(format string, args ...interface{})
func (l *Logger) Println(args ...interface{})type Option interface {
// unexported method
}
func WithDebug() Optionimport (
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"go.uber.org/zap"
"go.uber.org/zap/zapgrpc"
)
func main() {
zapLogger, _ := zap.NewProduction()
defer zapLogger.Sync()
// Replace gRPC's global logger
grpcLogger := zapgrpc.NewLogger(zapLogger)
grpclog.SetLoggerV2(grpcLogger)
// gRPC will now log through zap
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
// ...
}zapLogger, _ := zap.NewProduction()
grpcLogger := zapgrpc.NewLogger(zapLogger, zapgrpc.WithDebug())
grpclog.SetLoggerV2(grpcLogger)
// Print methods will log at debug level instead of infozapLogger, _ := zap.NewProduction()
grpcLogger := zapgrpc.NewLogger(
zapLogger.Named("grpc").With(zap.String("component", "grpc-client")),
)
grpclog.SetLoggerV2(grpcLogger)
// All gRPC logs will be tagged with logger name and component fieldImport: import "go.uber.org/zap/zapio"
Provides an io.Writer implementation that logs each write as a separate log entry.
type Writer struct {
Log *zap.Logger
Level zapcore.Level
}Writer that splits output on newlines and logs each line at the specified level.
Fields:
zapcore.InfoLevel)func (w *Writer) Write(p []byte) (n int, err error)
func (w *Writer) Sync() error
func (w *Writer) Close() errorio.Writer. Splits on '\n' and logs each line.import (
"os/exec"
"go.uber.org/zap"
"go.uber.org/zap/zapio"
"go.uber.org/zap/zapcore"
)
func runCommand(logger *zap.Logger) error {
writer := &zapio.Writer{
Log: logger.Named("command"),
Level: zapcore.InfoLevel,
}
defer writer.Close()
cmd := exec.Command("ls", "-la")
cmd.Stdout = writer
cmd.Stderr = writer
return cmd.Run()
// Each line of output is logged as separate Info entry
}func logResponse(logger *zap.Logger, resp *http.Response) error {
writer := &zapio.Writer{
Log: logger.With(zap.String("url", resp.Request.URL.String())),
Level: zapcore.DebugLevel,
}
defer writer.Close()
_, err := io.Copy(writer, resp.Body)
return err
// Response body lines logged at debug level
}import (
"log"
"go.uber.org/zap"
"go.uber.org/zap/zapio"
"go.uber.org/zap/zapcore"
)
func redirectStdLib(logger *zap.Logger) {
writer := &zapio.Writer{
Log: logger.Named("stdlib"),
Level: zapcore.InfoLevel,
}
log.SetOutput(writer)
log.SetFlags(0) // zap will add timestamps
log.Println("This goes through zap")
}Import: import "go.uber.org/zap/exp/zapfield"
Requires Go 1.18+
Experimental package providing generic field constructors using Go generics.
func Str[K ~string, V ~string](k K, v V) zap.FieldGeneric string field constructor. Accepts any types based on string.
func Strs[K ~string, V ~[]S, S ~string](k K, v V) zap.FieldGeneric string slice field constructor.
import (
"go.uber.org/zap"
"go.uber.org/zap/exp/zapfield"
)
type UserID string
type Username string
func logUser(logger *zap.Logger, id UserID, name Username) {
logger.Info("user action",
zapfield.Str("user_id", id), // Works with UserID type
zapfield.Str("username", name), // Works with Username type
)
}
type Tag string
func logTags(logger *zap.Logger, tags []Tag) {
logger.Info("tagged content",
zapfield.Strs("tags", tags), // Works with []Tag
)
}Import: import "go.uber.org/zap/exp/zapslog"
Requires Go 1.21+
Experimental package providing a slog.Handler implementation backed by zap, enabling integration with Go's structured logging package.
func NewHandler(core zapcore.Core, opts ...HandlerOption) *HandlerCreate a slog.Handler from a zapcore.Core.
Parameters:
core: Zapcore.Core to write log entries throughopts: Configuration optionstype Handler struct {
// unexported fields
}Implements slog.Handler interface.
func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool
func (h *Handler) Handle(ctx context.Context, record slog.Record) error
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler
func (h *Handler) WithGroup(name string) slog.HandlerStandard slog.Handler interface implementation.
type HandlerOption interface {
// unexported method
}
func WithCaller(enabled bool) HandlerOption
func WithCallerSkip(skip int) HandlerOption
func WithStacktrace(level slog.Level) HandlerOptionimport (
"log/slog"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
"go.uber.org/zap/zapcore"
)
func setupSlog() {
// Create zap core
zapLogger, _ := zap.NewProduction()
core := zapLogger.Core()
// Create slog handler from zap core
handler := zapslog.NewHandler(core)
// Create slog logger
logger := slog.New(handler)
// Use slog API, logs through zap
logger.Info("application started",
slog.String("version", "1.0.0"),
slog.Int("port", 8080),
)
}func setupSlogWithOptions() {
zapLogger, _ := zap.NewProduction()
handler := zapslog.NewHandler(
zapLogger.Core(),
zapslog.WithCaller(true),
zapslog.WithStacktrace(slog.LevelError),
)
logger := slog.New(handler)
logger.Error("error occurred") // Includes caller and stack trace
}func slogGroups() {
zapLogger, _ := zap.NewProduction()
handler := zapslog.NewHandler(zapLogger.Core())
logger := slog.New(handler)
logger.Info("user event",
slog.Group("user",
slog.String("id", "123"),
slog.String("name", "alice"),
),
slog.Group("metadata",
slog.String("ip", "192.168.1.1"),
slog.Time("timestamp", time.Now()),
),
)
// Nested structure logged through zap
}// Library uses slog
func libraryFunction(logger *slog.Logger) {
logger.Info("library operation",
slog.String("operation", "process"),
slog.Duration("duration", 100*time.Millisecond),
)
}
// Application uses zap
func main() {
zapLogger, _ := zap.NewProduction()
defer zapLogger.Sync()
// Wrap zap for slog-based library
handler := zapslog.NewHandler(zapLogger.Core())
slogLogger := slog.New(handler)
libraryFunction(slogLogger)
// Library logs go through zap
}// Phase 1: Tee to both loggers
oldLogger := grpclog.Component("component")
zapLogger, _ := zap.NewProduction()
grpcLogger := zapgrpc.NewLogger(zapLogger)
// Use grpcLogger but keep oldLogger for comparison// Core application uses zap
zapLogger, _ := zap.NewProduction()
// New modules use slog
handler := zapslog.NewHandler(zapLogger.Core())
slogLogger := slog.New(handler)
// Both log through same zap core
newModule.Run(slogLogger)
legacyModule.Run(zapLogger)zapLogger, _ := zap.NewProduction()
// Tee stdout and stderr to logger
stdoutWriter := &zapio.Writer{Log: zapLogger.Named("stdout"), Level: zapcore.InfoLevel}
stderrWriter := &zapio.Writer{Log: zapLogger.Named("stderr"), Level: zapcore.ErrorLevel}
cmd := exec.Command("./application")
cmd.Stdout = io.MultiWriter(os.Stdout, stdoutWriter)
cmd.Stderr = io.MultiWriter(os.Stderr, stderrWriter)
cmd.Run()
// Output visible on console AND logged through zaptype contextKey int
const userIDKey contextKey = 0
func enrichedHandler(core zapcore.Core) slog.Handler {
handler := zapslog.NewHandler(core)
// Wrap to add context values
return &contextHandler{
Handler: handler,
}
}
type contextHandler struct {
slog.Handler
}
func (h *contextHandler) Handle(ctx context.Context, r slog.Record) error {
if userID, ok := ctx.Value(userIDKey).(string); ok {
r.AddAttrs(slog.String("user_id", userID))
}
return h.Handler.Handle(ctx, r)
}