Uber Zap
Uberが開発した高性能で構造化されたレベル付きロギングライブラリ。67 ns/op、0アロケーションの性能とZerologに匹敵する速度を実現。カスタマイズ性においてZerologを上回り、Goエコシステムで人気。
GitHub概要
uber-go/zap
Blazing fast, structured, leveled logging in Go.
スター23,366
ウォッチ249
フォーク1,477
作成日:2016年2月18日
言語:Go
ライセンス:MIT License
トピックス
golangloggingstructured-loggingzap
スター履歴
データ取得日時: 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) // 実際のサーバー起動時のみ
}