Skip to content

Zap

Zap は Go で構築された高速で構造化されたレベル付きロギングコンポーネントです。

公式リポジトリ:uber-go/zap: Blazing fast, structured, leveled logging in Go. (github.com)

公式ドキュメント:zap package - go.uber.org/zap - Go Packages

インストール

go get -u go.uber.org/zap

クイックスタート

公式ドキュメントには 2 つのクイックスタート例が提供されています。どちらもプロダクションレベルのロガーです。1 つ目は printf スタイルをサポートしていますが、パフォーマンスは比較的低めです。

go
logger, _ := zap.NewProduction()
defer logger.Sync() // プログラム終了時にバッファされたログ書き込みをファイルにフラッシュ
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

2 つ目は logger で、より良いパフォーマンスを持ちますが、強く型付けされた出力のみをサポートしています。

go
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
  // 強く型付けされた Field 値としての構造化コンテキスト。
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

TIP

Zap の使い方は非常にシンプルです。難しい部分は、プロジェクトに適したロガーを設定することです。公式の例は少ないので、ソースコードのコメントをより多く読んでください。

設定

一般的に、ロガーの設定は設定ファイルで記述されます。Zap の設定も設定ファイルからのデシリアライゼーションをサポートしていますが、基本的な設定のみをサポートしています。高度な設定でさえ、公式の例は非常に簡潔で、プロダクション使用には不十分なので、詳細な設定について議論する必要があります。

まず、設定構造体全体を見てみましょう。各フィールドの意味を理解する必要があります。

go
type Config struct {
    // 最小ログレベル
   Level AtomicLevel `json:"level" yaml:"level"`
    // 開発モード、主にスタックトレースに影響
   Development bool `json:"development" yaml:"development"`
    // コーラー追跡
   DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // スタックトレース
   DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // サンプリング、ログパフォーマンスへの影響を制限しながら代表的なログのみを記録
   Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // エンコーディング、json または console モード
   Encoding string `json:"encoding" yaml:"encoding"`
    // エンコーダー設定、主に出力フォーマット設定
   EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // ログファイル出力パス
   OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // エラーファイル出力パス
   ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // ログにいくつかのデフォルト出力コンテンツを追加
   InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

以下はエンコーダー設定の詳細です

go
type EncoderConfig struct {
   // キー値、キーが空の場合、対応するプロパティは出力されません
   MessageKey     string `json:"messageKey" yaml:"messageKey"`
   LevelKey       string `json:"levelKey" yaml:"levelKey"`
   TimeKey        string `json:"timeKey" yaml:"timeKey"`
   NameKey        string `json:"nameKey" yaml:"nameKey"`
   CallerKey      string `json:"callerKey" yaml:"callerKey"`
   FunctionKey    string `json:"functionKey" yaml:"functionKey"`
   StacktraceKey  string `json:"stacktraceKey" yaml:"stacktraceKey"`
   SkipLineEnding bool   `json:"skipLineEnding" yaml:"skipLineEnding"`
   LineEnding     string `json:"lineEnding" yaml:"lineEnding"`
   // カスタムエンコーダー
   EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
   EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
   EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
   EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
   // ロガー名エンコーダー
   EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
   // リフレクションエンコーダー、主に interface{} 型用、デフォルトの jsonencoder がない場合
   NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
   // コンソール出力区切り文字列
   ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}

Option は設定スイッチとアプリケーションについてで、多くの実装があります。

go
type Option interface {
   apply(*Logger)
}

// Option 実装
type optionFunc func(*Logger)

func (f optionFunc) apply(log *Logger) {
  f(log)
}

// アプリケーション
func Development() Option {
  return optionFunc(func(log *Logger) {
    log.development = true
  })
}

これは最も一般的に使用されるロガーコアです。その内部フィールドは基本的に設定ステップを表しています。デシリアライズ設定の公式ステップを参照することもできます。ほぼ同じです。

go
type ioCore struct {
   // ログレベル
   LevelEnabler
   // ログエンコーディング
   enc Encoder
   // ログ書き込み
   out WriteSyncer
}

zap.Encoder はログのフォーマットとエンコードを担当します

zap.WriteSyncer はログ出力を担当し、主にファイルとコンソールへ

zap.LevelEnabler は最小ログレベルです。このレベル以下のログは syncer を通じて出力されません。

ログエンコーディング

ログエンコーディングは主にログのフォーマット詳細に関係します。まず、元のロガーを直接使用した出力を見てみましょう。

go
func TestQuickStart(t *testing.T) {
   rawJSON := []byte(`{
     "level": "debug",
     "encoding": "json",
     "outputPaths": ["stdout"],
     "errorOutputPaths": ["stderr"],
     "initialFields": {"foo": "bar"},
     "encoderConfig": {
       "messageKey": "message",
       "levelKey": "level",
       "levelEncoder": "lowercase"
     }
   }`)

   var cfg zap.Config
   if err := json.Unmarshal(rawJSON, &cfg); err != nil {
      panic(err)
   }
   logger := zap.Must(cfg.Build())
   defer logger.Sync()

   logger.Info("logger construction succeeded")
}
{"level":"info","message":"logger construction succeeded","foo":"bar"}

このログにはいくつかの問題があることがわかります:

  • タイムスタンプがない
  • コーラー情報がない、このログがどこから出力されたかわからず、エラーのトラブルシューティングが困難
  • スタックトレースがない

次に、これらの問題を段階的に解決しましょう。主に zapcore.EncoderConfig を修正します。まず、公式の直接デシリアライゼーションを使用せず、独自の設定ファイルを作成する必要があります。設定ファイル config.yml を作成します

yml
# Zap Logger Configuration
zap:
  prefix: ZapLogTest
  timeFormat: 2006/01/02 - 15:04:05.00000
  level: debug
  caller: true
  stackTrace: false
  encode: console
  # 出力先:file | console | both
  writer: both
  logFile:
    maxSize: 20
    backups: 5
    compress: true
    output:
      - "./log/output.log"

対応する構造体

go
// ZapConfig
// @Date: 2023-01-09 16:37:05
// @Description: zap logger configuration struct
type ZapConfig struct {
  Prefix     string         `yaml:"prefix" mapstructure:"prefix"`
  TimeFormat string         `yaml:"timeFormat" mapstructure:"timeFormat"`
  Level      string         `yaml:"level" mapstructure:"level"`
  Caller     bool           `yaml:"caller" mapstructure:"caller"`
  StackTrace bool           `yaml:"stackTrace" mapstructure:"stackTrace"`
  Writer     string         `yaml:"writer" mapstructure:"writer"`
  Encode     string         `yaml:"encode" mapstructure:"encode"`
  LogFile    *LogFileConfig `yaml:"logFile" mapstructure:"logFile"`
}

// LogFileConfig
// @Date: 2023-01-09 16:38:45
// @Description: log file configuration struct
type LogFileConfig struct {
  MaxSize  int      `yaml:"maxSize" mapstructure:"maxSize"`
  BackUps  int      `yaml:"backups" mapstructure:"backups"`
  Compress bool     `yaml:"compress" mapstructure:"compress"`
  Output   []string `yaml:"output" mapstructure:"output"`
  Errput   []string `yaml:"errput" mapstructure:"errput"`
}

TIP

設定の読み取りには Viper を使用します。コードは省略します。

go
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)

TimeEncoder は実際には関数です。公式が提供する他のタイムエンコーダーを使用することも、独自に作成することもできます。

go
func CustomTimeFormatEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
   encoder.AppendString(global.Config.ZapConfig.Prefix + "\t" + t.Format(global.Config.ZapConfig.TimeFormat))
}

全体部分は以下の通りです

go
func zapEncoder(config *ZapConfig) zapcore.Encoder {
   // 新しい設定を作成
   encoderConfig := zapcore.EncoderConfig{
      TimeKey:       "Time",
      LevelKey:      "Level",
      NameKey:       "Logger",
      CallerKey:     "Caller",
      MessageKey:    "Message",
      StacktraceKey: "StackTrace",
      LineEnding:    zapcore.DefaultLineEnding,
      FunctionKey:   zapcore.OmitKey,
   }
   // カスタム時間形式
   encoderConfig.EncodeTime = CustomTimeFormatEncoder
   // 大文字のログレベル
   encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
   // 秒単位の時間間隔
   encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
   // 短いコーラー出力
   encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
   // ロガー名の完全シリアライゼーション
   encoderConfig.EncodeName = zapcore.FullNameEncoder
   // 最終ログエンコーディング:json または console
   switch config.Encode {
   case "json":
      {
         return zapcore.NewJSONEncoder(encoderConfig)
      }
   case "console":
      {
         return zapcore.NewConsoleEncoder(encoderConfig)
      }
   }
   // デフォルト console
   return zapcore.NewConsoleEncoder(encoderConfig)
}

ログ出力

ログ出力はコンソール出力とファイル出力に分かれます。設定ファイルに基づいて動的に設定できます。ログファイルのローテーションが必要な場合は、別のサードパーティ依存関係を使用する必要があります。

go get -u github.com/natefinch/lumberjack

最終コードは以下の通りです

go
func zapWriteSyncer(cfg *ZapConfig) zapcore.WriteSyncer {
   syncers := make([]zapcore.WriteSyncer, 0, 2)
   // コンソール出力が有効な場合、コンソールライターを追加
   if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteConsole {
      syncers = append(syncers, zapcore.AddSync(os.Stdout))
   }

   // ファイルログが有効な場合、ファイルパスに基づいてライターを追加
   if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteFile {
      // ログ出力を追加
      for _, path := range cfg.LogFile.Output {
         logger := &lumberjack.Logger{
            Filename:   path, // ファイルパス
            MaxSize:    cfg.LogFile.MaxSize, // 分割ファイルのサイズ
            MaxBackups: cfg.LogFile.BackUps, // バックアップ数
            Compress:   cfg.LogFile.Compress, // 圧縮するかどうか
            LocalTime:  true, // 現地時間を使用
         }
         syncers = append(syncers, zapcore.Lock(zapcore.AddSync(logger)))
      }
   }
   return zap.CombineWriteSyncers(syncers...)
}

ログレベル

ログレベルには公式ドキュメントに列挙項目があります。直接使用してください。

go
func zapLevelEnabler(cfg *ZapConfig) zapcore.LevelEnabler {
   switch cfg.Level {
   case config.DebugLevel:
      return zap.DebugLevel
   case config.InfoLevel:
      return zap.InfoLevel
   case config.ErrorLevel:
      return zap.ErrorLevel
   case config.PanicLevel:
      return zap.PanicLevel
   case config.FatalLevel:
      return zap.FatalLevel
   }
   // デフォルト Debug レベル
   return zap.DebugLevel
}

最終ビルド

go
func InitZap(config *ZapConfig) *zap.Logger {
   // エンコーダーをビルド
   encoder := zapEncoder(config)
   // ログレベルをビルド
   levelEnabler := zapLevelEnabler(config)
   // 最後に Core と Options を取得
   subCore, options := tee(config, encoder, levelEnabler)
   // ロガーを作成
   return zap.New(subCore, options...)
}

// すべてをマージ
func tee(cfg *ZapConfig, encoder zapcore.Encoder, levelEnabler zapcore.LevelEnabler) (core zapcore.Core, options []zap.Option) {
   sink := zapWriteSyncer(cfg)
   return zapcore.NewCore(encoder, sink, levelEnabler), buildOptions(cfg, levelEnabler)
}

// オプションをビルド
func buildOptions(cfg *ZapConfig, levelEnabler zapcore.LevelEnabler) (options []zap.Option) {
   if cfg.Caller {
      options = append(options, zap.AddCaller())
   }

   if cfg.StackTrace {
      options = append(options, zap.AddStacktrace(levelEnabler))
   }
   return
}

最終結果

ZapLogTest      2023/01/09 - 19:44:00.91076     INFO    demo/zap.go:49     Logger initialization complete

Golang学习网由www.golangdev.cn整理维护