Skip to content

Zap

Zap adalah komponen logging yang cepat terstruktur dan berlevel yang dibangun dengan Go.

Repositori Resmi: uber-go/zap: Blazing fast, structured, leveled logging in Go. (github.com)

Dokumentasi Resmi: zap package - go.uber.org/zap - Go Packages

Instalasi

go get -u go.uber.org/zap

Memulai

Official memberikan dua contoh memulai cepat keduanya adalah logging tingkat produksi yang pertama adalah Sugar yang mendukung gaya printf namun performa relatif lebih rendah.

go
logger, _ := zap.NewProduction()
defer logger.Sync() // Sinkronisasi buffer ke file saat program berakhir
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

Yang kedua adalah performa lebih baik namun hanya mendukung output strongly typed logger

go
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
  // Context terstruktur sebagai Field values yang strongly typed.
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

TIP

Penggunaan Zap sangat sederhana titik merepotkan adalah mengkonfigurasi logging yang sesuai untuk proyek kita contoh official sedikit perlu banyak membaca komentar source code.

Konfigurasi

Umumnya konfigurasi logging ditulis dalam file konfigurasi konfigurasi Zap juga mendukung deserialisasi dari file konfigurasi namun hanya mendukung konfigurasi dasar meskipun konfigurasi tingkat tinggi contoh yang diberikan official juga sangat sederhana tidak cukup untuk digunakan jadi perlu menjelaskan detail konfigurasi secara detail.

Pertama lihat struktur tubuh konfigurasi keseluruhan perlu memahami arti setiap field di dalamnya

go
type Config struct {
    // Level logging minimum
   Level AtomicLevel `json:"level" yaml:"level"`
    // Mode development terutama mempengaruhi stack trace
   Development bool `json:"development" yaml:"development"`
    // Pelacakan caller
   DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // Stack trace
   DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // Sampling hanya mencatat sebagian log yang lebih representatif dalam kondisi membatasi penggunaan performa logging sama dengan logging selektif
   Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // Encoding dibagi menjadi dua mode json dan console
   Encoding string `json:"encoding" yaml:"encoding"`
    // Konfigurasi encoding terutama beberapa konfigurasi format output
   EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // Path output file logging
   OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // Path output file error
   ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // Menambahkan beberapa konten output default ke logging
   InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

Berikut adalah detail tentang konfigurasi encoding

go
type EncoderConfig struct {
   // Key value jika key kosong maka properti terkait tidak akan dioutput
   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"`
   // Beberapa encoder kustom
   EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
   EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
   EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
   EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
   // Encoder nama logger
   EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
   // Encoder refleksi terutama untuk tipe interface{} jika tidak ada default jsonencoder
   NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
   // String pemisah output console
   ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}

Option adalah tentang beberapa switch dan aplikasi konfigurasi ada banyak implementasi.

go
type Option interface {
   apply(*Logger)
}

// Implementasi Option
type optionFunc func(*Logger)

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

// Aplikasi
func Development() Option {
  return optionFunc(func(log *Logger) {
    log.development = true
  })
}

Ini adalah core logging yang paling sering digunakan field di dalamnya pada dasarnya mewakili langkah konfigurasi kita juga dapat merujuk pada langkah official saat deserialisasi konfigurasi kira-kira sama.

go
type ioCore struct {
   // Level logging
   LevelEnabler
   // Encoding logging
   enc Encoder
   // Penulisan logging
   out WriteSyncer
}

zap.Encoder bertanggung jawab untuk formatting dan encoding logging

zap.WriteSyncer bertanggung jawab untuk output logging terutama output ke file dan console

zap.LevelEnabler level logging minimum logging di bawah level ini tidak akan dioutput melalui syncer.

Encoding Logging

Encoding logging terutama melibatkan formatting beberapa detail logging pertama lihat output logging asli yang paling dasar.

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"}

Akan menemukan logging ini memiliki beberapa masalah:

  • Tidak ada waktu
  • Tidak ada situasi caller tidak tahu dari mana logging ini dioutput jika terjadi error tidak dapat troubleshooting
  • Tidak ada situasi stack

Selanjutnya langkah demi langkah untuk menyelesaikan masalah terutama melakukan transformasi pada zapcore.EncoderConfig pertama kita perlu menulis file konfigurasi sendiri tidak menggunakan deserialisasi langsung official. Pertama buat file konfigurasi sendiri config.yml

yml
# Konfigurasi Logging Zap
zap:
  prefix: ZapLogTest
  timeFormat: 2006/01/02 - 15:04:05.00000
  level: debug
  caller: true
  stackTrace: false
  encode: console
  # Output logging ke mana file | console | both
  writer: both
  logFile:
    maxSize: 20
    backups: 5
    compress: true
    output:
      - "./log/output.log"

Mapping ke struktur tubuh

go
// ZapConfig
// @Date: 2023-01-09 16:37:05
// @Description: Struktur tubuh konfigurasi logging zap
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: Struktur tubuh konfigurasi file logging
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

Membaca konfigurasi menggunakan Viper kode spesifik dihilangkan.

go
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)

TimerEncoder sebenarnya adalah fungsi kita dapat menggunakan encoder waktu lain yang disediakan official juga dapat menulis sendiri.

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

Bagian keseluruhan sebagai berikut

go
func zapEncoder(config *ZapConfig) zapcore.Encoder {
   // Membuat konfigurasi baru
   encoderConfig := zapcore.EncoderConfig{
      TimeKey:       "Time",
      LevelKey:      "Level",
      NameKey:       "Logger",
      CallerKey:     "Caller",
      MessageKey:    "Message",
      StacktraceKey: "StackTrace",
      LineEnding:    zapcore.DefaultLineEnding,
      FunctionKey:   zapcore.OmitKey,
   }
   // Format waktu kustom
   encoderConfig.EncodeTime = CustomTimeFormatEncoder
   // Level logging huruf besar
   encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
   // Interval waktu detik
   encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
   // Output caller singkat
   encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
   // Serialisasi nama logger lengkap
   encoderConfig.EncodeName = zapcore.FullNameEncoder
   // Encoding logging akhir json atau console
   switch config.Encode {
   case "json":
      {
         return zapcore.NewJSONEncoder(encoderConfig)
      }
   case "console":
      {
         return zapcore.NewConsoleEncoder(encoderConfig)
      }
   }
   // Default console
   return zapcore.NewConsoleEncoder(encoderConfig)
}

Output Logging

Output logging dibagi menjadi output console dan output file kita dapat melakukan konfigurasi dinamis sesuai file konfigurasi dan jika ingin melakukan pemotongan file logging perlu menggunakan dependensi pihak ketiga lain.

go get -u github.com/natefinch/lumberjack

Kode akhir sebagai berikut

go
 func zapWriteSyncer(cfg *ZapConfig) zapcore.WriteSyncer {
   syncers := make([]zapcore.WriteSyncer, 0, 2)
   // Jika mengaktifkan output console logging tambahkan writer console
   if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteConsole {
      syncers = append(syncers, zapcore.AddSync(os.Stdout))
   }

   // Jika mengaktifkan penyimpanan file logging tambahkan writer sesuai path file
   if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteFile {
      // Tambahkan output logging
      for _, path := range cfg.LogFile.Output {
         logger := &lumberjack.Logger{
            Filename:   path, // Path file
            MaxSize:    cfg.LogFile.MaxSize, // Ukuran file pemotongan
            MaxBackups: cfg.LogFile.BackUps, // Jumlah backup
            Compress:   cfg.LogFile.Compress, // Apakah kompresi
            LocalTime:  true, // Menggunakan waktu lokal
         }
         syncers = append(syncers, zapcore.Lock(zapcore.AddSync(logger)))
      }
   }
   return zap.CombineWriteSyncers(syncers...)
}

Level Logging

Official memiliki enum tentang level logging langsung gunakan saja.

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
   }
   // Default level Debug
   return zap.DebugLevel
}

Build Akhir

go
func InitZap(config *ZapConfig) *zap.Logger {
   // Build encoder
   encoder := zapEncoder(config)
   // Build level logging
   levelEnabler := zapLevelEnabler(config)
   // Core dan Options akhir
   subCore, options := tee(config, encoder, levelEnabler)
    // Membuat Logger
   return zap.New(subCore, options...)
}

// Menggabungkan semua
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)
}

// Build Option
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
}

Efek akhir

ZapLogTest      2023/01/09 - 19:44:00.91076     INFO    demo/zap.go:49     Logging inisialisasi selesai

Golang by www.golangdev.cn edit