Uber Zap

Uberが開発した高性能で構造化されたレベル付きロギングライブラリ。67 ns/op、0アロケーションの性能とZerologに匹敵する速度を実現。カスタマイズ性においてZerologを上回り、Goエコシステムで人気。

ロギングGoGolang構造化ログパフォーマンスデバッグ

GitHub概要

uber-go/zap

Blazing fast, structured, leveled logging in Go.

スター23,366
ウォッチ249
フォーク1,477
作成日:2016年2月18日
言語:Go
ライセンス:MIT License

トピックス

golangloggingstructured-loggingzap

スター履歴

uber-go/zap Star History
データ取得日時: 2025/7/17 08:20

ライブラリ

Zap

概要

Zapは「Blazing fast, structured, leveled logging in Go」として開発されたGo言語向けの高性能ロギングライブラリです。Uber社によって開発・保守されており、圧倒的なパフォーマンスと最小限のメモリ割り当てを実現します。マイクロサービスからリソース制約のある環境まで、あらゆるGo アプリケーションに対応し、構造化ログとプレーンテキストログの両方をサポートします。

詳細

Zap 1.27.0は2025年現在も活発に開発されているGo界のデファクトスタンダードロギングライブラリです。高性能を最重要視し、競合するライブラリの4-10倍の速度を誇り、ガベージコレクションのオーバーヘッドを最小化します。開発環境向けの読みやすいコンソール出力と本番環境向けのJSON構造化出力をサポートし、動的ログレベル制御と型安全なフィールドシステムを提供。

主な特徴

  • 圧倒的なパフォーマンス: ベンチマークで証明済みの高速性、競合ライブラリの4-10倍高速
  • メモリ効率: アロケーションフリーなロギングでGCオーバーヘッドを最小化
  • 構造化ログ: ネイティブJSON出力による検索性と分析性の向上
  • 2つのロガー: 高性能なLogger(型安全)と使いやすいSugaredLogger
  • 柔軟な設定: 開発・本番環境に最適化された設定プリセット
  • 動的レベル制御: 実行時のログレベル変更とフィルタリング

メリット・デメリット

メリット

  • Go界で最も高速なロギングライブラリ(ベンチマーク証明済み)
  • アロケーションフリー設計による卓越したメモリ効率
  • 構造化ログによる優れた検索性と分析機能
  • Uber社による安定したメンテナンスと実績
  • 2つのロガータイプによる柔軟な使い分け(性能 vs 利便性)
  • 豊富なエコシステムと活発なコミュニティ

デメリット

  • 他のロギングライブラリと比較してAPIがやや複雑
  • 型安全なLoggerの使用には詳細な型指定が必要
  • プロダクション設定の最適化には深い理解が必要
  • 古いGoバージョンのサポートが制限される(最新2バージョンのみ)
  • エラーハンドリングとSync()呼び出しの管理が必要
  • 複雑な設定時の初期セットアップコストが高い

参考ページ

書き方の例

基本的なセットアップ

# Zapパッケージのインストール
go get go.uber.org/zap

# 追加パッケージ(必要に応じて)
go get go.uber.org/zap/zapcore
go get gopkg.in/natefinch/lumberjack.v2  # ログローテーション用

シンプルなロガー使用

package main

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

func main() {
    // プロダクション向けロガー(JSON出力)
    logger, _ := zap.NewProduction()
    defer logger.Sync() // プログラム終了前に必ずSync()を呼ぶ

    // 構造化ログ出力(型安全)
    url := "https://example.com"
    logger.Info("URLの取得に失敗しました",
        zap.String("url", url),
        zap.Int("attempt", 3),
        zap.Duration("backoff", time.Second),
    )

    // SugaredLoggerを使用(使いやすさ重視)
    sugar := logger.Sugar()
    
    // 構造化ログ(キー・バリューペア)
    sugar.Infow("URLの取得に失敗しました",
        "url", url,
        "attempt", 3,
        "backoff", time.Second,
    )
    
    // printf形式のログ
    sugar.Infof("URLの取得に失敗しました: %s", url)
}

開発・本番環境の設定

package main

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

func main() {
    var logger *zap.Logger
    
    // 環境に応じたロガー設定
    if os.Getenv("ENV") == "production" {
        // プロダクション設定(JSON形式)
        logger, _ = zap.NewProduction()
    } else {
        // 開発設定(読みやすいコンソール出力)
        logger, _ = zap.NewDevelopment()
    }
    defer logger.Sync()

    // カスタム設定例
    config := zap.NewProductionConfig()
    config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
    config.OutputPaths = []string{"stdout", "app.log"}
    config.ErrorOutputPaths = []string{"stderr", "error.log"}
    
    customLogger, _ := config.Build()
    defer customLogger.Sync()

    // ログレベル別出力例
    logger.Debug("デバッグメッセージ")
    logger.Info("情報メッセージ")
    logger.Warn("警告メッセージ")
    logger.Error("エラーメッセージ")
    logger.DPanic("開発環境でのpanicメッセージ")

    // フィールド付きログ
    logger.Info("ユーザー操作",
        zap.String("user_id", "user123"),
        zap.String("action", "login"),
        zap.Time("timestamp", time.Now()),
        zap.Bool("success", true),
    )
}

高度なロガー設定とカスタマイズ

package main

import (
    "os"
    "time"
    
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
)

func main() {
    // カスタムエンコーダー設定
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "timestamp",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        MessageKey:     "message",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.StringDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    }

    // ログローテーション設定(lumberjack使用)
    w := zapcore.AddSync(&lumberjack.Logger{
        Filename:   "/var/log/myapp/app.log",
        MaxSize:    500, // メガバイト
        MaxBackups: 3,   // 保持するバックアップ数
        MaxAge:     28,  // 日数
        Compress:   true, // 圧縮
    })

    // 複数の出力先設定
    consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
    fileEncoder := zapcore.NewJSONEncoder(encoderConfig)
    
    core := zapcore.NewTee(
        zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
        zapcore.NewCore(fileEncoder, w, zapcore.InfoLevel),
    )

    // カスタムロガー作成
    logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
    defer logger.Sync()

    // 使用例
    logger.Info("アプリケーション開始",
        zap.String("version", "1.0.0"),
        zap.String("environment", os.Getenv("ENV")),
    )
}

Webアプリケーションでの統合

package main

import (
    "context"
    "net/http"
    "time"
    
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

// ミドルウェア用のロガー設定
func setupLogger() *zap.Logger {
    config := zap.NewProductionConfig()
    config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    logger, _ := config.Build()
    return logger
}

// HTTPリクエストロギングミドルウェア
func loggingMiddleware(logger *zap.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            
            // リクエスト情報をログ
            logger.Info("HTTPリクエスト開始",
                zap.String("method", r.Method),
                zap.String("url", r.URL.String()),
                zap.String("remote_addr", r.RemoteAddr),
                zap.String("user_agent", r.UserAgent()),
            )

            // リクエスト処理
            next.ServeHTTP(w, r)

            // レスポンス情報をログ
            duration := time.Since(start)
            logger.Info("HTTPリクエスト完了",
                zap.String("method", r.Method),
                zap.String("url", r.URL.String()),
                zap.Duration("duration", duration),
                zap.Int64("duration_ms", duration.Milliseconds()),
            )
        })
    }
}

// コンテキストベースロギング
func handleUser(logger *zap.Logger) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        userID := r.URL.Query().Get("user_id")
        
        // ユーザー固有のロガー作成
        userLogger := logger.With(
            zap.String("user_id", userID),
            zap.String("request_id", generateRequestID()),
        )

        ctx := context.WithValue(r.Context(), "logger", userLogger)
        r = r.WithContext(ctx)

        userLogger.Info("ユーザー情報取得開始")

        // ビジネスロジック
        if userID == "" {
            userLogger.Warn("ユーザーIDが指定されていません")
            http.Error(w, "User ID required", http.StatusBadRequest)
            return
        }

        // データベース操作などのシミュレーション
        time.Sleep(50 * time.Millisecond)

        userLogger.Info("ユーザー情報取得完了",
            zap.String("status", "success"),
        )

        w.WriteHeader(http.StatusOK)
        w.Write([]byte("User info retrieved"))
    }
}

func generateRequestID() string {
    return "req_" + time.Now().Format("20060102150405")
}

func main() {
    logger := setupLogger()
    defer logger.Sync()

    mux := http.NewServeMux()
    mux.Handle("/user", loggingMiddleware(logger)(handleUser(logger)))

    logger.Info("サーバー開始", zap.Int("port", 8080))
    http.ListenAndServe(":8080", mux)
}

エラーハンドリングとパフォーマンス最適化

package main

import (
    "context"
    "errors"
    "sync"
    "time"
    
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func main() {
    // パフォーマンス最適化されたロガー設定
    config := zap.NewProductionConfig()
    config.Sampling = &zap.SamplingConfig{
        Initial:    100,
        Thereafter: 100,
    }
    logger, _ := config.Build()
    defer logger.Sync()

    // エラーハンドリングの例
    if err := processData(logger); err != nil {
        logger.Error("データ処理エラー",
            zap.Error(err),
            zap.String("operation", "processData"),
            zap.Time("timestamp", time.Now()),
        )
    }

    // 高頻度ログのパフォーマンステスト
    performanceTest(logger)
}

func processData(logger *zap.Logger) error {
    logger.Info("データ処理開始")

    // 処理中にエラーが発生
    if err := doSomething(); err != nil {
        // エラーをラップしてコンテキスト情報を追加
        logger.Error("処理中にエラーが発生",
            zap.Error(err),
            zap.String("step", "doSomething"),
            zap.Int("retry_count", 0),
        )
        return err
    }

    logger.Info("データ処理完了")
    return nil
}

func doSomething() error {
    return errors.New("simulated error")
}

func performanceTest(logger *zap.Logger) {
    // 高頻度ログのパフォーマンステスト
    start := time.Now()
    const iterations = 100000

    var wg sync.WaitGroup
    wg.Add(iterations)

    for i := 0; i < iterations; i++ {
        go func(index int) {
            defer wg.Done()
            
            // 条件付きログ(レベルチェックによる最適化)
            if logger.Core().Enabled(zapcore.DebugLevel) {
                logger.Debug("パフォーマンステスト",
                    zap.Int("iteration", index),
                    zap.String("test_type", "concurrent"),
                )
            }

            if index%1000 == 0 {
                logger.Info("進捗状況",
                    zap.Int("completed", index),
                    zap.Float64("progress", float64(index)/float64(iterations)*100),
                )
            }
        }(i)
    }

    wg.Wait()
    duration := time.Since(start)

    logger.Info("パフォーマンステスト完了",
        zap.Int("iterations", iterations),
        zap.Duration("total_duration", duration),
        zap.Float64("logs_per_second", float64(iterations)/duration.Seconds()),
    )
}

動的ログレベル制御

package main

import (
    "net/http"
    "sync/atomic"
    
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

type DynamicLogger struct {
    *zap.Logger
    level *zap.AtomicLevel
}

func NewDynamicLogger() *DynamicLogger {
    level := zap.NewAtomicLevel()
    level.SetLevel(zap.InfoLevel)

    config := zap.NewProductionConfig()
    config.Level = level

    logger, _ := config.Build()

    return &DynamicLogger{
        Logger: logger,
        level:  &level,
    }
}

func (dl *DynamicLogger) SetLevel(level zapcore.Level) {
    dl.level.SetLevel(level)
    dl.Info("ログレベルが変更されました",
        zap.String("new_level", level.String()),
    )
}

func (dl *DynamicLogger) GetLevel() zapcore.Level {
    return dl.level.Level()
}

// HTTPエンドポイントでログレベル制御
func (dl *DynamicLogger) handleLevelChange(w http.ResponseWriter, r *http.Request) {
    levelStr := r.URL.Query().Get("level")
    
    var level zapcore.Level
    if err := level.UnmarshalText([]byte(levelStr)); err != nil {
        dl.Error("無効なログレベル",
            zap.String("level", levelStr),
            zap.Error(err),
        )
        http.Error(w, "Invalid log level", http.StatusBadRequest)
        return
    }

    dl.SetLevel(level)
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Log level updated to " + level.String()))
}

func main() {
    dl := NewDynamicLogger()
    defer dl.Sync()

    // 動的レベル変更のテスト
    dl.Debug("これはデバッグメッセージです") // 初期状態では出力されない
    dl.Info("これは情報メッセージです")      // 出力される

    // レベルをデバッグに変更
    dl.SetLevel(zap.DebugLevel)
    
    dl.Debug("これはデバッグメッセージです") // 今度は出力される
    dl.Info("これは情報メッセージです")      // 引き続き出力される

    // HTTPサーバーでの動的制御
    http.HandleFunc("/log-level", dl.handleLevelChange)
    
    dl.Info("動的ログレベル制御サーバー開始", zap.Int("port", 8080))
    // http.ListenAndServe(":8080", nil) // 実際のサーバー起動時のみ
}