Zerolog

Goで最も高速な構造化ロギングフレームワーク。35 ns/op、0アロケーションの優秀なパフォーマンスを実現。CrowdStrike等の企業で採用され、ベンチマークでほぼ全シナリオで最高速度を記録している。

ロギングライブラリGoGolangゼロアロケーションJSON高性能構造化ログ

GitHub概要

rs/zerolog

Zero Allocation JSON Logger

スター11,612
ウォッチ72
フォーク595
作成日:2017年5月12日
言語:Go
ライセンス:MIT License

トピックス

golangjsonloggingstructured-loggingzerolog

スター履歴

rs/zerolog Star History
データ取得日時: 2025/7/17 08:20

ライブラリ

Zerolog

概要

ZerologはGo言語向けのゼロアロケーションJSONロガーで、超高速なログ処理と最小限のメモリ使用量を実現する構造化ロギングライブラリです。JSON-firstアプローチによりマシン可読性に優れ、チェーン式APIによる直感的な操作性を提供します。2025年現在、Go生態系で最高レベルのパフォーマンスを誇り、高負荷システムやマイクロサービス環境での標準的なロギングソリューションとして広く採用されています。

詳細

Zerolog 2025年版は、ゼロアロケーション設計により従来比5-10倍の高速ログ処理を実現し、CPUオーバーヘッドを最小化します。JSON専用出力によりELK Stack、Prometheus、Grafana等の監視ツールとの完全統合、コンテキスト対応のサブロガー機能、カスタムフィールドマーシャラー、フック機能による拡張性を提供。CBOR (Concise Binary Object Representation)バイナリエンコーディング対応により更なる高速化とサイズ削減を実現し、分散システムでの大量ログ処理に最適化されています。

主な特徴

  • ゼロアロケーション設計: メモリアロケーションを最小化した超高速処理
  • JSON-first出力: 完全なJSON形式による機械可読ログ
  • チェーン式API: 直感的で可読性の高い流暢なインターフェース
  • レベル別サンプリング: 高負荷時のログ量制御機能
  • コンテキスト統合: Go Contextとの完全統合
  • CBOR対応: バイナリエンコーディングによる更なる高速化

メリット・デメリット

メリット

  • Go生態系で最高レベルのパフォーマンス(5-10倍高速)
  • ゼロアロケーション設計によるメモリ効率性
  • JSON出力による監視・分析ツールとの高い親和性
  • シンプルで直感的なチェーン式API
  • コンテキスト対応により分散システムでの追跡が容易
  • CBOR対応による更なる性能向上とサイズ削減
  • 軽量で外部依存関係が最小限

デメリット

  • JSON専用のため人間による直接的な可読性が低い
  • LogrusやZapと比較して機能の豊富さで劣る部分がある
  • プレーンテキスト出力が必要な環境では追加設定が必要
  • 複雑なフォーマッターやフック機能が限定的
  • デバッグ時はConsoleWriterによる可読化が必要
  • 学習リソースが他のロガーと比較してやや少ない

参考ページ

書き方の例

インストールと基本セットアップ

# Goモジュールにzerologを追加
go get -u github.com/rs/zerolog/log

# CBOR バイナリエンコーディング使用時
go build -tags binary_log .
package main

import (
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func main() {
	// UNIXタイムスタンプ(高速・軽量)
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix

	// 基本的なログ出力
	log.Print("hello world")
	log.Info().Msg("情報メッセージ")
	log.Debug().Msg("デバッグメッセージ")
	log.Warn().Msg("警告メッセージ")
	log.Error().Msg("エラーメッセージ")
	
	// 構造化ログ(推奨)
	log.Info().
		Str("user_id", "12345").
		Str("action", "login").
		Str("ip", "192.168.1.100").
		Msg("ユーザーログイン")
	
	// 複数フィールドのログ
	log.Debug().
		Str("Scale", "833 cents").
		Float64("Interval", 833.09).
		Msg("フィボナッチはいたるところに")
	
	// フィールドのみでメッセージなし
	log.Debug().
		Str("Name", "Tom").
		Send()
}

// 出力例:
// {"time":1516134303,"level":"debug","message":"hello world"}
// {"level":"info","time":1494567715,"user_id":"12345","action":"login","ip":"192.168.1.100","message":"ユーザーログイン"}
// {"level":"debug","Scale":"833 cents","Interval":833.09,"time":1562212768,"message":"フィボナッチはいたるところに"}
// {"level":"debug","Name":"Tom","time":1562212768}

ログレベル制御と設定管理

package main

import (
	"flag"
	"os"
	
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func init() {
	// グローバル設定のカスタマイズ
	zerolog.TimestampFieldName = "timestamp"
	zerolog.LevelFieldName = "severity"
	zerolog.MessageFieldName = "message"
	zerolog.ErrorFieldName = "error"
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
	
	// 期間フィールドの単位設定
	zerolog.DurationFieldUnit = time.Millisecond
	zerolog.DurationFieldInteger = true
}

func main() {
	// コマンドラインフラグによるレベル制御
	debug := flag.Bool("debug", false, "デバッグレベルログを有効化")
	flag.Parse()

	// デフォルトレベル設定
	zerolog.SetGlobalLevel(zerolog.InfoLevel)
	if *debug {
		zerolog.SetGlobalLevel(zerolog.DebugLevel)
	}

	// 環境変数による設定
	if os.Getenv("LOG_LEVEL") == "trace" {
		zerolog.SetGlobalLevel(zerolog.TraceLevel)
	}

	// 全ログレベルの使用例
	log.Trace().Msg("トレースレベル(最詳細)")
	log.Debug().Msg("デバッグレベル(開発時のみ表示)")
	log.Info().Msg("情報レベル(通常の動作時表示)")
	log.Warn().Msg("警告レベル(注意が必要)")
	log.Error().Msg("エラーレベル(エラー発生時)")
	// log.Fatal().Msg("致命的レベル(プログラム終了)")
	// log.Panic().Msg("パニックレベル(panic()実行)")

	// 条件付きログ(パフォーマンス最適化)
	if e := log.Debug(); e.Enabled() {
		// デバッグレベルが有効な場合のみ実行される高負荷な処理
		expensiveValue := performExpensiveOperation()
		e.Str("result", expensiveValue).Msg("高負荷な処理の結果")
	}
	
	// ログレベル無効化(完全停止)
	// zerolog.SetGlobalLevel(zerolog.Disabled)
}

func performExpensiveOperation() string {
	// 計算コストの高い処理のシミュレーション
	return "expensive_result"
}

カスタムロガーとコンテキスト管理

package main

import (
	"context"
	"os"
	"time"
	
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func main() {
	// カスタムロガーインスタンスの作成
	logger := zerolog.New(os.Stderr).With().Timestamp().Logger()

	// グローバルロガーの永続的コンテキスト追加
	log.Logger = log.With().
		Str("service", "user-service").
		Str("version", "v1.2.3").
		Logger()

	// サブロガーの作成(コンポーネント別)
	dbLogger := log.With().
		Str("component", "database").
		Logger()
	
	apiLogger := log.With().
		Str("component", "api").
		Logger()

	// コンテキスト付きロガー
	requestLogger := log.With().
		Str("request_id", "req-123456").
		Str("user_id", "user-789").
		Logger()

	// 基本的なログ出力
	logger.Info().Str("foo", "bar").Msg("hello world")
	
	// コンポーネント別ログ
	dbLogger.Info().
		Str("operation", "SELECT").
		Str("table", "users").
		Dur("duration", 45*time.Millisecond).
		Msg("データベース操作完了")
	
	apiLogger.Warn().
		Int("status_code", 429).
		Str("endpoint", "/api/users").
		Msg("レート制限に達しました")

	// リクエスト別ログ
	requestLogger.Info().Msg("リクエスト処理開始")
	requestLogger.Debug().Str("method", "POST").Msg("HTTPメソッド確認")
	requestLogger.Info().Msg("リクエスト処理完了")

	// Go Context との統合
	ctx := context.Background()
	
	// ロガーをコンテキストに埋め込み
	ctx = logger.WithContext(ctx)
	
	// 他の関数にコンテキストを渡す
	processRequest(ctx)
}

func processRequest(ctx context.Context) {
	// コンテキストからロガーを取得
	logger := zerolog.Ctx(ctx)
	
	logger.Info().
		Str("function", "processRequest").
		Msg("リクエスト処理関数を実行")
	
	// 追加のコンテキスト情報
	logger.Debug().
		Str("trace_id", "trace-abc123").
		Str("span_id", "span-def456").
		Msg("分散トレーシング情報")
}

高度なフィールド型と辞書構造

package main

import (
	"errors"
	"os"
	"time"
	
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func main() {
	// 標準型フィールド
	log.Info().
		Str("string_field", "文字列値").
		Bool("bool_field", true).
		Int("int_field", 123).
		Int8("int8_field", 8).
		Int16("int16_field", 16).
		Int32("int32_field", 32).
		Int64("int64_field", 64).
		Uint("uint_field", 456).
		Uint8("uint8_field", 8).
		Uint16("uint16_field", 16).
		Uint32("uint32_field", 32).
		Uint64("uint64_field", 64).
		Float32("float32_field", 3.14).
		Float64("float64_field", 2.718281828).
		Msg("標準型フィールドの例")

	// 高度なフィールド型
	err := errors.New("サンプルエラー")
	now := time.Now()
	duration := 150 * time.Millisecond
	
	log.Error().
		Err(err).                                    // エラーフィールド
		Time("event_time", now).                     // 時刻フィールド
		Dur("response_time", duration).              // 期間フィールド
		Timestamp().                                 // タイムスタンプ自動追加
		Hex("binary_data", []byte{0xde, 0xad, 0xbe, 0xef}). // バイナリデータ(16進)
		RawJSON("raw_json", []byte(`{"key":"value"}`)).      // 生JSON
		Msg("高度なフィールド型の例")

	// スライス型フィールド
	log.Info().
		Strs("string_array", []string{"a", "b", "c"}).
		Ints("int_array", []int{1, 2, 3}).
		Bools("bool_array", []bool{true, false, true}).
		Errs("error_array", []error{
			errors.New("エラー1"),
			errors.New("エラー2"),
		}).
		Msg("スライス型フィールドの例")

	// 辞書構造(ネストされたオブジェクト)
	log.Info().
		Str("service", "user-management").
		Dict("user", zerolog.Dict().
			Str("name", "田中太郎").
			Int("age", 30).
			Str("email", "[email protected]").
			Dict("address", zerolog.Dict().
				Str("country", "日本").
				Str("city", "東京").
				Str("postal_code", "100-0001"),
			),
		).
		Dict("request", zerolog.Dict().
			Str("method", "POST").
			Str("path", "/api/users").
			Int("status_code", 201).
			Dur("duration", 123*time.Millisecond),
		).
		Msg("ユーザー作成完了")

	// インターフェース型(リフレクション使用)
	complexData := map[string]interface{}{
		"metadata": map[string]interface{}{
			"version": "1.0",
			"tags":    []string{"production", "api"},
		},
		"metrics": map[string]interface{}{
			"cpu_usage":    85.5,
			"memory_usage": 1024,
		},
	}
	
	log.Debug().
		Interface("complex_data", complexData).
		Msg("複雑なデータ構造のログ")

	// 関数による動的フィールド
	log.Info().
		Func(func(e *zerolog.Event) {
			// この関数はログレベルが有効な場合のみ実行される
			systemInfo := getSystemInfo()
			e.Str("hostname", systemInfo.Hostname)
			e.Float64("cpu_load", systemInfo.CPULoad)
			e.Int64("memory_free", systemInfo.MemoryFree)
		}).
		Msg("システム情報")
}

type SystemInfo struct {
	Hostname   string
	CPULoad    float64
	MemoryFree int64
}

func getSystemInfo() SystemInfo {
	// システム情報取得の実装(簡略化)
	return SystemInfo{
		Hostname:   "web-server-01",
		CPULoad:    23.5,
		MemoryFree: 2048 * 1024 * 1024,
	}
}

ConsoleWriterと出力カスタマイズ

package main

import (
	"os"
	"strings"
	"fmt"
	"time"
	
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func setupDevelopmentLogger() {
	// 開発環境用:人間可読な出力
	output := zerolog.ConsoleWriter{Out: os.Stderr}
	log.Logger = log.Output(output)
}

func setupCustomConsoleWriter() {
	// カスタムフォーマット設定
	output := zerolog.ConsoleWriter{
		Out:        os.Stdout,
		TimeFormat: time.RFC3339,
		NoColor:    false, // カラー出力有効
	}
	
	// レベル表示のカスタマイズ
	output.FormatLevel = func(i interface{}) string {
		return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
	}
	
	// メッセージ表示のカスタマイズ
	output.FormatMessage = func(i interface{}) string {
		return fmt.Sprintf("***%s***", i)
	}
	
	// フィールド名のカスタマイズ
	output.FormatFieldName = func(i interface{}) string {
		return fmt.Sprintf("%s:", i)
	}
	
	// フィールド値のカスタマイズ
	output.FormatFieldValue = func(i interface{}) string {
		return strings.ToUpper(fmt.Sprintf("%s", i))
	}
	
	logger := zerolog.New(output).With().Timestamp().Logger()
	
	logger.Info().Str("foo", "bar").Msg("Hello World")
	// 出力例: 2006-01-02T15:04:05Z07:00 | INFO  | ***Hello World*** foo:BAR
}

func setupAdvancedConsoleWriter() {
	// 高度なカスタマイズ:フィールド順序制御
	output := zerolog.ConsoleWriter{
		Out:     os.Stdout,
		NoColor: true,
		PartsOrder: []string{
			"level", "service", "component", "request_id", "message",
		},
		FieldsExclude: []string{"service", "component", "request_id"},
	}
	
	// パーツごとの値フォーマット
	output.FormatPartValueByName = func(i interface{}, s string) string {
		var ret string
		switch s {
		case "service":
			ret = strings.ToUpper(fmt.Sprintf("[%s]", i))
		case "component":
			ret = strings.ToLower(fmt.Sprintf("(%s)", i))
		case "request_id":
			ret = fmt.Sprintf("req:%s", i)
		default:
			ret = fmt.Sprintf("%s", i)
		}
		return ret
	}
	
	logger := zerolog.New(output)
	
	logger.Info().
		Str("service", "api").
		Str("component", "auth").
		Str("request_id", "123").
		Str("user", "tanaka").
		Msg("Authentication successful")
	// 出力例: INFO [API] (auth) req:123 Authentication successful user:tanaka
}

func setupMultipleOutputs() {
	// 複数出力先の設定
	consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
	
	// JSON(ファイル) + 人間可読(コンソール)
	multi := zerolog.MultiLevelWriter(consoleWriter, os.Stderr)
	
	logger := zerolog.New(multi).With().Timestamp().Logger()
	
	logger.Info().Msg("Hello World!")
	
	// 出力:
	// Line 1 (Console): 12:36PM INF Hello World!
	// Line 2 (Stderr):  {"level":"info","time":"2019-11-07T12:36:38+03:00","message":"Hello World!"}
}

func main() {
	// 環境による出力切り替え
	env := os.Getenv("ENVIRONMENT")
	
	switch env {
	case "development":
		setupDevelopmentLogger()
	case "testing":
		setupCustomConsoleWriter()
	case "staging":
		setupAdvancedConsoleWriter()
	case "demo":
		setupMultipleOutputs()
	default:
		// 本番環境:JSON出力
		zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
	}
	
	// ログ出力テスト
	log.Info().Str("foo", "bar").Msg("テストメッセージ")
	log.Warn().Int("code", 404).Msg("リソースが見つかりません")
	log.Error().Err(errors.New("test error")).Msg("エラーが発生しました")
}

サンプリングとパフォーマンス最適化

package main

import (
	"time"
	
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func basicSampling() {
	// 基本的なサンプリング:10回に1回ログ出力
	sampled := log.Sample(&zerolog.BasicSampler{N: 10})
	
	// 100回ループして10回だけログが出力される
	for i := 0; i < 100; i++ {
		sampled.Info().Int("iteration", i).Msg("サンプリングされたメッセージ")
	}
}

func advancedSampling() {
	// レベル別サンプリング設定
	sampled := log.Sample(zerolog.LevelSampler{
		// デバッグレベル:1秒間に5回までバースト許可、その後100回に1回
		DebugSampler: &zerolog.BurstSampler{
			Burst:       5,
			Period:      1 * time.Second,
			NextSampler: &zerolog.BasicSampler{N: 100},
		},
		// 情報レベル:サンプリングなし(全て出力)
		InfoSampler: nil,
		// エラーレベル:サンプリングなし(全て出力)
		ErrorSampler: nil,
	})
	
	// 高頻度デバッグログの例
	for i := 0; i < 1000; i++ {
		sampled.Debug().Int("loop", i).Msg("高頻度デバッグ")
		sampled.Info().Int("important", i).Msg("重要な情報")
		
		if i%100 == 0 {
			sampled.Error().Int("checkpoint", i).Msg("チェックポイント")
		}
		
		time.Sleep(10 * time.Millisecond)
	}
}

func disableSampling() {
	// サンプリングの動的無効化
	zerolog.DisableSampling(true)
	
	// これにより、すべてのサンプリングされたロガーが
	// 100%のイベントを出力するようになる
	sampled := log.Sample(&zerolog.BasicSampler{N: 10})
	
	for i := 0; i < 20; i++ {
		sampled.Info().Int("all_logged", i).Msg("全てログ出力される")
	}
	
	// サンプリング再有効化
	zerolog.DisableSampling(false)
}

func performanceOptimizedLogging() {
	// パフォーマンス最適化設定
	zerolog.TimeFieldFormat = zerolog.TimeFormatUnix  // UNIX時間(最高速)
	zerolog.DurationFieldUnit = time.Millisecond      // 期間の単位
	zerolog.DurationFieldInteger = true               // 期間を整数で出力
	
	// 高速ロガー設定
	logger := zerolog.New(os.Stdout)
	
	// ベンチマーク用の高速ログ出力
	start := time.Now()
	
	for i := 0; i < 10000; i++ {
		logger.Info().
			Int("iteration", i).
			Str("operation", "benchmark").
			Dur("elapsed", time.Since(start)).
			Msg("高速ログテスト")
	}
	
	total := time.Since(start)
	logger.Info().
		Int("total_logs", 10000).
		Dur("total_time", total).
		Float64("logs_per_second", float64(10000)/total.Seconds()).
		Msg("ベンチマーク完了")
}

func main() {
	log.Info().Msg("=== 基本サンプリング ===")
	basicSampling()
	
	log.Info().Msg("=== 高度なサンプリング ===")
	advancedSampling()
	
	log.Info().Msg("=== サンプリング無効化 ===")
	disableSampling()
	
	log.Info().Msg("=== パフォーマンス最適化 ===")
	performanceOptimizedLogging()
}

フックとエラーハンドリング

package main

import (
	"errors"
	"fmt"
	"context"
	"os"
	
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"github.com/pkg/errors"
)

// カスタムフック:重要度フィールド追加
type SeverityHook struct{}

func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
	if level != zerolog.NoLevel {
		e.Str("severity", level.String())
	}
}

// トレーシングフック:分散トレーシング情報追加
type TracingHook struct{}

func (h TracingHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
	ctx := e.GetCtx()
	if ctx != nil {
		if spanID := getSpanIDFromContext(ctx); spanID != "" {
			e.Str("span_id", spanID)
		}
		if traceID := getTraceIDFromContext(ctx); traceID != "" {
			e.Str("trace_id", traceID)
		}
	}
}

func getSpanIDFromContext(ctx context.Context) string {
	// 実際のトレーシングライブラリからSpan IDを取得
	// ここでは簡略化
	return "span-12345"
}

func getTraceIDFromContext(ctx context.Context) string {
	// 実際のトレーシングライブラリからTrace IDを取得
	// ここでは簡略化
	return "trace-abcdef"
}

func setupErrorStackTrace() {
	// スタックトレース付きエラーログ
	zerolog.ErrorStackMarshaler = func(err error) interface{} {
		// github.com/pkg/errors との統合
		type stackTracer interface {
			StackTrace() errors.StackTrace
		}
		
		if err, ok := err.(stackTracer); ok {
			return err.StackTrace()
		}
		return nil
	}
}

func demonstrateErrorLogging() {
	// 基本的なエラーログ
	err := errors.New("データベース接続エラー")
	log.Error().Err(err).Msg("エラーが発生")
	
	// スタックトレース付きエラー
	err = errors.Wrap(errors.New("内部エラー"), "外部処理でエラー")
	log.Error().Stack().Err(err).Msg("スタックトレース付きエラー")
}

func demonstrateHooks() {
	// フック付きロガーの作成
	logger := log.Hook(SeverityHook{}).Hook(TracingHook{})
	
	ctx := context.Background()
	
	// フック適用ログ
	logger.Info().Ctx(ctx).Msg("フック機能テスト")
	logger.Warn().Ctx(ctx).Msg("警告メッセージ")
	logger.Error().Ctx(ctx).Msg("エラーメッセージ")
}

func setupErrorHandler() {
	// カスタムエラーハンドラー設定
	zerolog.ErrorHandler = func(err error) {
		fmt.Fprintf(os.Stderr, "Zerolog書き込みエラー: %v\n", err)
	}
}

func demonstrateCallerInfo() {
	// 呼び出し元情報の設定
	
	// 短い形式(ファイル名:行番号)
	zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
		return fmt.Sprintf("%s:%d", filepath.Base(file), line)
	}
	
	// 呼び出し元情報付きロガー
	logger := log.With().Caller().Logger()
	
	logger.Info().Msg("呼び出し元情報付きログ")
	// 出力例: {"level":"info","caller":"main.go:123","message":"呼び出し元情報付きログ"}
	
	// フルパス形式
	zerolog.CallerMarshalFunc = nil // デフォルトに戻す
	logger = log.With().Caller().Logger()
	
	logger.Info().Msg("フルパス呼び出し元情報")
	// 出力例: {"level":"info","caller":"/go/src/project/main.go:126","message":"フルパス呼び出し元情報"}
}

func businessLogicWithLogging(ctx context.Context) error {
	logger := zerolog.Ctx(ctx)
	
	logger.Info().Msg("ビジネスロジック開始")
	
	// 何らかの処理でエラーが発生
	if err := performDatabaseOperation(); err != nil {
		logger.Error().
			Err(err).
			Str("operation", "database_query").
			Msg("データベース操作でエラー")
		return errors.Wrap(err, "ビジネスロジックでエラー")
	}
	
	logger.Info().Msg("ビジネスロジック完了")
	return nil
}

func performDatabaseOperation() error {
	// データベース操作のシミュレーション
	return errors.New("接続タイムアウト")
}

func main() {
	// 初期設定
	setupErrorStackTrace()
	setupErrorHandler()
	
	// フック機能のデモ
	log.Info().Msg("=== フック機能デモ ===")
	demonstrateHooks()
	
	// エラーログのデモ
	log.Info().Msg("=== エラーログデモ ===")
	demonstrateErrorLogging()
	
	// 呼び出し元情報のデモ
	log.Info().Msg("=== 呼び出し元情報デモ ===")
	demonstrateCallerInfo()
	
	// コンテキスト付きビジネスロジックのデモ
	log.Info().Msg("=== ビジネスロジックデモ ===")
	ctx := log.With().
		Str("request_id", "req-789").
		Str("user_id", "user-456").
		Logger().WithContext(context.Background())
	
	if err := businessLogicWithLogging(ctx); err != nil {
		log.Error().Err(err).Msg("ビジネスロジックエラー")
	}
}