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/zapMemulai
Official memberikan dua contoh memulai cepat keduanya adalah logging tingkat produksi yang pertama adalah Sugar yang mendukung gaya printf namun performa relatif lebih rendah.
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
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
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
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.
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.
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.
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
# 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
// 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.
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)TimerEncoder sebenarnya adalah fungsi kita dapat menggunakan encoder waktu lain yang disediakan official juga dapat menulis sendiri.
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
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/lumberjackKode akhir sebagai berikut
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.
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
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