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/zapHı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:
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:
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:
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:
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:
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:
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:
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:
# 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:
// 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.
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:
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:
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/lumberjackSon kod aşağıdaki gibidir:
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:
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
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