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เริ่มต้นอย่างรวดเร็ว
ทางอย่างเป็นทางการให้ตัวอย่างเริ่มต้นอย่างรวดเร็วสองตัวอย่าง ทั้งสองเป็นบันทึกระดับผลิตภัณฑ์ ตัวอย่างแรกเป็น Sugar ที่รองรับสไตล์ printf แต่ประสิทธิภาพค่อนข้างต่ำ
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)ตัวอย่างที่สองเป็น logger ที่มีประสิทธิภาพดีกว่า แต่รองรับเฉพาะเอาต์พุตแบบ strongly typed
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)TIP
การใช้งาน Zap ง่ายมาก จุดที่ยุ่งยากคือการกำหนดค่าล็อกเกอร์ให้เหมาะกับโปรเจกต์ของตัวเอง ตัวอย่างทางการมีน้อย ต้องอ่านคำอธิบายประกอบในซอร์สโค้ดให้มาก
การกำหนดค่า
โดยทั่วไปแล้วการกำหนดค่าล็อกเกอร์มักเขียนในไฟล์การกำหนดค่า การกำหนดค่าของ Zap ก็รองรับการแยกวิเคราะห์จากไฟล์การกำหนดค่าเช่นกัน แต่รองรับเฉพาะการกำหนดค่าพื้นฐาน แม้แต่การกำหนดค่าขั้นสูงที่ทางอย่างเป็นทางการให้ตัวอย่างก็เรียบง่ายมาก ไม่เพียงพอที่จะนำไปใช้งาน ดังนั้นจะต้องอธิบายรายละเอียดการกำหนดค่าอย่างละเอียด
ก่อนอื่นดูโครงสร้างการกำหนดค่าโดยรวม ต้องเข้าใจความหมายของแต่ละฟิลด์ก่อน
type Config struct {
// ระดับบันทึกข้อมูลต่ำสุด
Level AtomicLevel `json:"level" yaml:"level"`
// โหมดพัฒนา มีผลต่อการติดตาม stack trace เป็นหลัก
Development bool `json:"development" yaml:"development"`
// ติดตามผู้เรียก
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// ติดตาม stack trace
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"`
}ต่อไปนี้เป็นรายละเอียดเกี่ยวกับการกำหนดค่าการเข้ารหัส
type EncoderConfig struct {
// คีย์ค่า หาก key เป็นว่าง คุณสมบัติที่เกี่ยวข้องจะไม่ถูกเอาต์พุต
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"`
// ตัวเข้ารหัส reflection ส่วนใหญ่สำหรับประเภท interface{} หากไม่มี jsonencoder เริ่มต้น
NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
// สตริงระยะห่างเอาต์พุตคอนโซล
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}Option เป็นสวิตช์และการประยุกต์ใช้การกำหนดค่าบางอย่าง มีการใช้งานมากมาย
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
})
}นี่คือคอร์ล็อกเกอร์ที่ใช้บ่อยที่สุด ฟิลด์ภายใน基本上แสดงขั้นตอนการกำหนดค่าของเรา สามารถอ้างอิงขั้นตอนที่ทางอย่างเป็นทางการให้ไว้เมื่อแยกวิเคราะห์การกำหนดค่า โดยประมาณเหมือนกัน
type ioCore struct {
// ระดับล็อกเกอร์
LevelEnabler
// การเข้ารหัสล็อกเกอร์
enc Encoder
// การเขียนล็อกเกอร์
out WriteSyncer
}zap.Encoder รับผิดชอบการจัดรูปแบบและการเข้ารหัสล็อกเกอร์
zap.WriteSyncer รับผิดชอบการเอาต์พุตล็อกเกอร์ ส่วนใหญ่เป็นการเอาต์พุตลงไฟล์และคอนโซล
zap.LevelEnabler ระดับล็อกเกอร์ต่ำสุด ล็อกเกอร์ที่ต่ำกว่าระดับนี้จะไม่ถูกเอาต์พุตผ่าน syncer
การเข้ารหัสล็อกเกอร์
การเข้ารหัสล็อกเกอร์เกี่ยวข้องกับการจัดรูปแบบรายละเอียดของล็อกเกอร์ ก่อนอื่นดูการเอาต์พุตล็อกเกอร์ดั้งเดิมที่สุดโดยตรง
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"}จะพบว่าล็อกเกอร์บรรทัดนี้มีปัญหาหลายประการ:
- ไม่มีเวลา
- ไม่มีสถานการณ์ผู้เรียก ไม่รู้ว่าล็อกเกอร์บรรทัดนี้ถูกเอาต์พุตจากที่ไหน ไม่อย่างนั้นเมื่อเกิดข้อผิดพลาดจะไม่สามารถตรวจสอบได้
- ไม่มีสถานการณ์ stack
ต่อไปนี้จะแก้ปัญหาทีละขั้นตอน ส่วนใหญ่เป็นการปรับปรุง zapcore.EncoderConfig ก่อนอื่นเราต้องเขียนไฟล์การกำหนดค่าเอง ไม่ใช้การแยกวิเคราะห์โดยตรงจากทางอย่างเป็นทางการ ก่อนอื่นสร้างไฟล์การกำหนดค่า config.yml
# การกำหนดค่าล็อกเกอร์ Zap
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"โครงสร้างที่แมป
// ZapConfig
// @Date: 2023-01-09 16:37:05
// @Description: โครงสร้างการกำหนดค่าล็อกเกอร์ 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: โครงสร้างการกำหนดค่าไฟล์ล็อกเกอร์
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 รหัสโดยละเอียดถูกละเว้น
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)TimerEncoder ที่จริงแล้วเป็นฟังก์ชัน เราสามารถใช้ตัวเข้ารหัสเวลาอื่นที่ทางอย่างเป็นทางการให้ไว้ หรือเขียนเองก็ได้
func CustomTimeFormatEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(global.Config.ZapConfig.Prefix + "\t" + t.Format(global.Config.ZapConfig.TimeFormat))
}ส่วนรวมมีดังนี้
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
// การ serialize ชื่อ logger แบบเต็ม
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)
}การเอาต์พุตล็อกเกอร์
การเอาต์พุตล็อกเกอร์แบ่งเป็นการเอาต์พุตคอนโซลและการเอาต์พุตไฟล์ เราสามารถกำหนดค่าแบบไดนามิกตามไฟล์การกำหนดค่า และหากต้องการทำการตัดไฟล์ล็อกเกอร์ยังต้องใช้ dependency ของบุคคลที่สามอีกตัว
go get -u github.com/natefinch/lumberjackสุดท้ายรหัสมีดังนี้
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...)
}ระดับล็อกเกอร์
ทางอย่างเป็นทางการมีรายการ enum เกี่ยวกับระดับล็อกเกอร์ ใช้ได้โดยตรง
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
}สุดท้ายสร้าง
func InitZap(config *ZapConfig) *zap.Logger {
// สร้างตัวเข้ารหัส
encoder := zapEncoder(config)
// สร้างระดับล็อกเกอร์
levelEnabler := zapLevelEnabler(config)
// สุดท้ายได้ Core และ Options
subCore, options := tee(config, encoder, levelEnabler)
// สร้าง Logger
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)
}
// สร้าง 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
}สุดท้ายผลลัพธ์
ZapLogTest 2023/01/09 - 19:44:00.91076 INFO demo/zap.go:49 ล็อกเกอร์เริ่มต้นเสร็จสิ้น