Skip to content

Zap

Zap Go ile oluşturulmuş hızlı yapılandırılmış seviyeli bir günlük bileşenidir.

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

Resmi Dokümantasyon: zap package - go.uber.org/zap - Go Packages

Kurulum

go get -u go.uber.org/zap

Hızlı Başlangıç

Resmi dokümantasyon iki hızlı başlangıç örneği sağlar. İkisi de üretim seviyesinde logger'lardır. İlki printf stilini destekleyen ancak performansı nispeten daha düşük olan Sugar'dır:

go
logger, _ := zap.NewProduction()
defer logger.Sync() // Program çıktığında buffer'daki günlük yazılarını dosyaya yaz
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

İkincisi daha iyi performansa sahip olan ancak yalnızca güçlü tipli çıktıyı destekleyen logger'dır:

go
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
  // Güçlü tipli Field değerleri olarak yapılandırılmış bağlam.
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

TIP

Zap kullanımı çok basittir. Zorlu kısım projeniz için uygun bir logger yapılandırmaktır. Resmi örnekler az olduğundan kaynak kod yorumlarını daha fazla okuyun.

Yapılandırma

Genellikle logger yapılandırması yapılandırma dosyalarında yazılır. Zap'ın yapılandırması da yapılandırma dosyalarından serileştirmeyi destekler ancak yalnızca temel yapılandırmayı destekler. İleri düzey yapılandırmalar için bile resmi örnekler çok öz ve üretim kullanımı için yeterli değildir bu nedenle detaylı yapılandırmayı tartışmamız gerekir.

İlk olarak genel yapılandırma struct'ına bakalım. Her alanın anlamını anlamamız gerekir:

go
type Config struct {
    // Minimum günlük seviyesi
   Level AtomicLevel `json:"level" yaml:"level"`
    // Geliştirme modu. Öncelikle yığın izlerini etkiler
   Development bool `json:"development" yaml:"development"`
    // Çağrı izleme
   DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // Yığın izleri
   DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // Örnekleme. Yalnızca temsilci günlükleri kaydederek günlük performans etkisini sınırlama
   Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // Kodlama. json veya console modu
   Encoding string `json:"encoding" yaml:"encoding"`
    // Encoder yapılandırması. Öncelikle çıktı formatlandırma yapılandırması
   EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // Günlük dosyası çıktı yolları
   OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // Hata dosyası çıktı yolları
   ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // Günlüklere bazı varsayılan çıktı içeriği ekle
   InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

Aşağıda encoder yapılandırması hakkında detaylar bulunmaktadır:

go
type EncoderConfig struct {
   // Anahtar değeri. Eğer anahtar boşsa ilgili özellik çıktılanmayacaktır
   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"`
   // Bazı özel encoder'lar
   EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
   EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
   EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
   EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
   // Logger adı encoder
   EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
   // Yansıma encoder'ı. Öncelikle interface{} türleri için. Varsayılan jsonencoder yoksa
   NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
   // Console çıktı ayırıcı string
   ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}

Option yapılandırma anahtarları ve uygulamaları hakkında olup birçok implementasyona sahiptir:

go
type Option interface {
   apply(*Logger)
}

// Option implementasyonu
type optionFunc func(*Logger)

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

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

Bu en yaygın kullanılan logger çekirdeğidir. İç alanları temel olarak yapılandırma adımlarımızı temsil eder. Yapılandırma serileştirmesi için resmi adımlara da bakabilirsiniz. Aşağı yukarı aynıdır:

go
type ioCore struct {
   // Günlük seviyesi
   LevelEnabler
   // Günlük kodlaması
   enc Encoder
   // Günlük yazma
   out WriteSyncer
}

zap.Encoder günlük formatlandırma ve kodlamadan sorumludur

zap.WriteSyncer günlük çıktısından sorumludur. Öncelikle dosya ve console'a

zap.LevelEnabler minimum günlük seviyesidir. Bu seviyenin altındaki günlükler artık syncer aracılığıyla çıktılanmaz.

Günlük Kodlama

Günlük kodlama öncelikle günlüklerin formatlama detaylarını içerir. İlk olarak orijinal logger'ı doğrudan kullanarak çıktıya bakalım:

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

Bu günlükte birkaç sorun olduğunu göreceksiniz:

  • Zaman damgası yok
  • Çağrı bilgisi yok. Bu günlüğün nereden çıktılanacağını bilmiyoruz. Bu da hataları gidermeyi zorlaştırıyor
  • Yığın izi yok

Sonra bu sorunları adım adım çözelim. Öncelikle zapcore.EncoderConfig'u değiştirerek. İlk olarak resmi doğrudan serileştirmeyi kullanmak yerine kendi yapılandırma dosyamızı yazmamız gerekiyor. config.yml adında bir yapılandırma dosyası oluşturun:

yml
# Zap Logger Yapılandırması
zap:
  prefix: ZapLogTest
  timeFormat: 2006/01/02 - 15:04:05.00000
  level: debug
  caller: true
  stackTrace: false
  encode: console
  # Günlükleri nereye yazmalı: file | console | both
  writer: both
  logFile:
    maxSize: 20
    backups: 5
    compress: true
    output:
      - "./log/output.log"

Eşlenen struct:

go
// ZapConfig
// @Date: 2023-01-09 16:37:05
// @Description: zap logger yapılandırma 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: günlük dosyası yapılandırma 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

Yapılandırmayı okumak için Viper kullanın. Kod burada atlanmıştır.

go
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)

TimeEncoder aslında bir fonksiyondur. Resmi tarafından sağlanan diğer zaman encoder'larını kullanabilir veya kendimiz yazabiliriz:

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

Genel bölüm aşağıdaki gibidir:

go
func zapEncoder(config *ZapConfig) zapcore.Encoder {
   // Yeni yapılandırma oluştur
   encoderConfig := zapcore.EncoderConfig{
      TimeKey:       "Time",
      LevelKey:      "Level",
      NameKey:       "Logger",
      CallerKey:     "Caller",
      MessageKey:    "Message",
      StacktraceKey: "StackTrace",
      LineEnding:    zapcore.DefaultLineEnding,
      FunctionKey:   zapcore.OmitKey,
   }
   // Özel zaman formatı
   encoderConfig.EncodeTime = CustomTimeFormatEncoder
   // Günlük seviyesi büyük harfle
   encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
   // Saniye seviyesi zaman aralığı
   encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
   // Kısa çağrı çıktısı
   encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
   // Logger adının tam serileştirilmesi
   encoderConfig.EncodeName = zapcore.FullNameEncoder
   // Son günlük kodlama: json veya console
   switch config.Encode {
   case "json":
      {
         return zapcore.NewJSONEncoder(encoderConfig)
      }
   case "console":
      {
         return zapcore.NewConsoleEncoder(encoderConfig)
      }
   }
   // Varsayılan console
   return zapcore.NewConsoleEncoder(encoderConfig)
}

Günlük Çıktısı

Günlük çıktısı console çıktısı ve dosya çıktısı olarak ikiye ayrılır. Yapılandırma dosyasına göre dinamik olarak yapılandırabiliriz. Günlük dosyası döndürme istiyorsak başka bir üçüncü taraf bağımlılığı kullanmamız gerekir:

go get -u github.com/natefinch/lumberjack

Son kod aşağıdaki gibidir:

go
func zapWriteSyncer(cfg *ZapConfig) zapcore.WriteSyncer {
   syncers := make([]zapcore.WriteSyncer, 0, 2)
   // Console çıktısı etkinse console yazar ekle
   if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteConsole {
      syncers = append(syncers, zapcore.AddSync(os.Stdout))
   }

   // Dosya günlüğü etkinse dosya yollarına göre yazar ekle
   if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteFile {
      // Günlük çıktısı ekle
      for _, path := range cfg.LogFile.Output {
         logger := &lumberjack.Logger{
            Filename:   path, // Dosya yolu
            MaxSize:    cfg.LogFile.MaxSize, // Bölünmüş dosyaların boyutu
            MaxBackups: cfg.LogFile.BackUps, // Yedekleme sayısı
            Compress:   cfg.LogFile.Compress, // Sıkıştırma
            LocalTime:  true, // Yerel zaman kullan
         }
         syncers = append(syncers, zapcore.Lock(zapcore.AddSync(logger)))
      }
   }
   return zap.CombineWriteSyncers(syncers...)
}

Günlük Seviyesi

Resmi dokümantasyonda günlük seviyeleri için enum öğeleri vardır. Doğrudan kullanın:

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
   }
   // Varsayılan Debug seviyesi
   return zap.DebugLevel
}

Son Oluşturma

go
func InitZap(config *ZapConfig) *zap.Logger {
   // Encoder oluştur
   encoder := zapEncoder(config)
   // Günlük seviyesi oluştur
   levelEnabler := zapLevelEnabler(config)
   // Son olarak Core ve Options al
   subCore, options := tee(config, encoder, levelEnabler)
   // Logger oluştur
   return zap.New(subCore, options...)
}

// Her şeyi birleştir
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)
}

// Options Oluştur
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
}

Son sonuç:

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

Golang by www.golangdev.cn edit