1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. Logging
  5. Zap - Golang learning step 6-2

Zap - Golang learning step 6-2

  • 公開日
  • カテゴリ:Logging
  • タグ:Golang,roadmap.sh,学習メモ
Zap - Golang learning step 6-2

roadmap.sh > Go > Logging > Zap の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. Zap パッケージ
    1. Zap の特徴
  4. インストール
  5. 基本的な使用例
  6. 開発向けの簡略ロガー (SugaredLogger)
  7. SugaredLogger と Logger の違い
  8. カスタム設定
  9. フィールドの型
  10. エラーハンドリング
  11. パフォーマンスの考慮点
  12. Logger と SugaredLogger の使い分け
    1. Logger を選択すべき場合

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

Zap パッケージ

Zap は、Uber が開発した高速かつ構造化されたロギングを提供する Go 用のロギングライブラリです。

Zap の特徴

  1. 高パフォーマンス
    • メモリアロケーションを最小限に抑えた設計
    • JSON 形式のログ出力に最適化
  2. レベルベースのロギング
    • Debug, Info, Warn, Error など一般的なログレベルをサポート
  3. 構造化ログ
    • フィールドベースの柔軟なログ出力
    • JSON 形式での出力が容易
  4. 柔軟な設定
    • 開発モード用の「SugaredLogger」と、プロダクションモード用の「Logger」という2つのロガーを提供

インストール

Zap は go get コマンドを使ってインストールします。

go get -u go.uber.org/zap

基本的な使用例

以下のコードは、Zap の基本的な使い方を示しています。

package main

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

func main() {
  // デフォルトの Logger を作成
  logger, _ := zap.NewProduction() // プロダクション用
  defer logger.Sync()              // ログをフラッシュしてリソースを解放

  // 構造化ログの記録
  logger.Info("これは情報レベルのログです",
    zap.String("key", "value"),
    zap.Int("attempt", 3),
    zap.Duration("backoff", 2),
  )

  // エラーレベルのログ
  logger.Error("エラーが発生しました",
    zap.String("原因", "無効な入力"),
  )
}

出力:

# 構造化ログの記録
{"level":"info","ts":1733832023.54071,"caller":"main.go:16","msg":"これは情報レベルのログです","key":"value","attempt":3,"backoff":0.000000002}

# エラーレベルのログ
{"level":"error","ts":1733832023.5408251,"caller":"main.go:23","msg":"エラーが発生しました","原因":"無効な入力","stacktrace":"main.main01\n\t/path/to/root.main\n\t/path/to/root/main.go:6\nruntime.main\n\t/path/to/root/.goenv/versions/1.23.2/src/runtime/proc.go:272"}

開発向けの簡略ロガー (SugaredLogger)

開発時には、もう少し簡単に使える SugaredLogger が便利です。文字列のフォーマットを使ってログを記録できます。

package main

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

func main() {
  // Logger を作成
  logger, _ := zap.NewDevelopment()
  defer logger.Sync()

  // Logger を SugaredLogger に変換
  sugar := logger.Sugar()

  // フォーマットを使ったログ
  sugar.Infof("ユーザー %s が %d 回目の試行に成功しました", "Alice", 3)

  // その他の簡略ログ
  sugar.Infow("試行成功",
    "user", "Alice",
    "attempt", 3,
  )
}

出力:

2024-12-10T21:13:20.783+0900    INFO    main.go:58      ユーザー Alice が 3 回目の試行に成功しました
2024-12-10T21:13:20.783+0900    INFO    main.go:61      試行成功        {"user": "Alice", "attempt": 3}

SugaredLogger と Logger の違い

  • SugaredLogger:
    • フォーマット文字列(例: "%s")や非構造化なログをサポート。
    • 使いやすいが、構造化ログのパフォーマンスが若干低下する場合がある。
  • Logger:
    • 高性能な構造化ログ用。
    • フィールド付きログ(例: zap.String("key", "value"))を使う。
// 通常の Logger
logger, _ := zap.NewProduction()
logger.Info("メッセージ", zap.Int("count", 5))

// SugaredLogger
sugar := logger.Sugar()
sugar.Infof("カウント: %d", 5)

カスタム設定

Logger の動作をカスタマイズする例:

package main

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

func main() {
    config := zap.Config{
    Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
    Development: false,
    Encoding:    "json",
    EncoderConfig: zapcore.EncoderConfig{
      TimeKey:       "time",
      LevelKey:      "level",
      NameKey:       "logger",
      MessageKey:    "msg",
      StacktraceKey: "stacktrace",
      EncodeTime:    zapcore.ISO8601TimeEncoder,
      EncodeLevel:   zapcore.LowercaseLevelEncoder,
    },
    OutputPaths:      []string{"stdout"},
    ErrorOutputPaths: []string{"stderr"},
  }

    logger, _ := config.Build()
    defer logger.Sync()

    logger.Info("アプリケーションが起動しました")
}

出力:

{"level":"info","time":"2024-12-10T21:22:01.560+0900","msg":"アプリケーションが起動しました"}

フィールドの型

Zap は様々な型のフィールドをサポートしています。

logger, _ := zap.NewDevelopment()
defer logger.Sync()

logger.Info("詳細情報",
    zap.String("name", "テスト"),
    zap.Int("age", 25),
    zap.Bool("active", true),
    zap.Float64("score", 85.5),
    zap.Duration("elapsed", time.Second*10),
)

出力:

2024-12-10T21:25:39.371+0900    INFO    main.go:81      詳細情報        {"name": "テスト", "age": 25, "active": true, "score": 85.5, "elapsed": "10s"}

エラーハンドリング

エラー情報をログ出力する例

package main

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

// エラーを返す処理を模擬するための関数
func doSomething() error {
    return errors.New("何かしらのエラーが発生")
}

func someFunction() error {
    // Logger の作成(実際のアプリケーションではグローバルに保持することが多い)
    logger, err := zap.NewProduction()
    if err != nil {
        return err
    }
    defer logger.Sync()

    err = doSomething()
    if err != nil {
        // エラー情報を構造化してログ出力
        logger.Error("処理に失敗しました",
            zap.Error(err),
            zap.String("function", "someFunction"),
        )
        return err
    }
    return nil
}

func main() {
    if err := someFunction(); err != nil {
        // main 関数でのエラーハンドリング
        // この例では単純にプログラムを終了させていますが、
        // 実際のアプリケーションでは適切なエラーハンドリングを行ってください
        return
    }
}

このコードを実行すると、以下のような JSON 形式のエラーログが出力されます。

{
  "level": "error",
  "ts": 1702184400.123456,
  "caller": "example/main.go:23",
  "msg": "処理に失敗しました",
  "error": "何かしらのエラーが発生",
  "function": "someFunction"
}

パフォーマンスの考慮点

  1. Logger は通常アプリケーション起動時に 1 度だけ作成し、グローバル変数や依存性注入で使い回す
  2. defer logger.Sync() を使用してバッファされたログを確実にフラッシュする
  3. 不要なログレベルは設定で無効化することでパフォーマンスを改善できる

Logger と SugaredLogger の使い分け

Logger と SugaredLogger はそれぞれ特徴があり、使用状況によって適切な選択が異なります。以下のポイントを考慮して選択してください。

Logger を選択すべき場合

  1. パフォーマンスが重要視される場合
    • 高スループットが要求される本番環境
    • 大量のログ出力が予想される場合
    • メモリ使用量を最小限に抑えたい場合
  2. 構造化ログが必要な場合
    • ログの解析や集計が必要な場合
    • ELK スタックなどのログ分析基盤と連携する場合
    • JSON 形式での出力が要件の場合
  3. 型安全性が重要な場合
    • コンパイル時の型チェックを活用したい場合
    • フィールド名のタイプミスを防ぎたい場合

まとめ

  • Zap は高速かつ構造化されたロギングを提供する Go 用のライブラリ
  • Logger と SugaredLogger の 2 種類のロガーを提供
  • 高性能な構造化ログを JSON 形式で出力可能
  • 開発向けの簡易ロガーとして SugaredLogger を利用可能
  • 柔軟なカスタマイズが可能な設定オプションを提供
  • フィールドベースのログで型安全性を確保
  • ログレベルの設定で不要なログを抑制可能
  • パフォーマンスと使いやすさを両立した設計


[Next] Step 6-3: Zerolog

[Prev] Step 6-1: log/slog

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級