Skip to content

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 แต่ประสิทธิภาพค่อนข้างต่ำ

go
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

go
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 ก็รองรับการแยกวิเคราะห์จากไฟล์การกำหนดค่าเช่นกัน แต่รองรับเฉพาะการกำหนดค่าพื้นฐาน แม้แต่การกำหนดค่าขั้นสูงที่ทางอย่างเป็นทางการให้ตัวอย่างก็เรียบง่ายมาก ไม่เพียงพอที่จะนำไปใช้งาน ดังนั้นจะต้องอธิบายรายละเอียดการกำหนดค่าอย่างละเอียด

ก่อนอื่นดูโครงสร้างการกำหนดค่าโดยรวม ต้องเข้าใจความหมายของแต่ละฟิลด์ก่อน

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

ต่อไปนี้เป็นรายละเอียดเกี่ยวกับการกำหนดค่าการเข้ารหัส

go
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 เป็นสวิตช์และการประยุกต์ใช้การกำหนดค่าบางอย่าง มีการใช้งานมากมาย

go
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
  })
}

นี่คือคอร์ล็อกเกอร์ที่ใช้บ่อยที่สุด ฟิลด์ภายใน基本上แสดงขั้นตอนการกำหนดค่าของเรา สามารถอ้างอิงขั้นตอนที่ทางอย่างเป็นทางการให้ไว้เมื่อแยกวิเคราะห์การกำหนดค่า โดยประมาณเหมือนกัน

go
type ioCore struct {
   // ระดับล็อกเกอร์
   LevelEnabler
   // การเข้ารหัสล็อกเกอร์
   enc Encoder
   // การเขียนล็อกเกอร์
   out WriteSyncer
}

zap.Encoder รับผิดชอบการจัดรูปแบบและการเข้ารหัสล็อกเกอร์

zap.WriteSyncer รับผิดชอบการเอาต์พุตล็อกเกอร์ ส่วนใหญ่เป็นการเอาต์พุตลงไฟล์และคอนโซล

zap.LevelEnabler ระดับล็อกเกอร์ต่ำสุด ล็อกเกอร์ที่ต่ำกว่าระดับนี้จะไม่ถูกเอาต์พุตผ่าน syncer

การเข้ารหัสล็อกเกอร์

การเข้ารหัสล็อกเกอร์เกี่ยวข้องกับการจัดรูปแบบรายละเอียดของล็อกเกอร์ ก่อนอื่นดูการเอาต์พุตล็อกเกอร์ดั้งเดิมที่สุดโดยตรง

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

จะพบว่าล็อกเกอร์บรรทัดนี้มีปัญหาหลายประการ:

  • ไม่มีเวลา
  • ไม่มีสถานการณ์ผู้เรียก ไม่รู้ว่าล็อกเกอร์บรรทัดนี้ถูกเอาต์พุตจากที่ไหน ไม่อย่างนั้นเมื่อเกิดข้อผิดพลาดจะไม่สามารถตรวจสอบได้
  • ไม่มีสถานการณ์ stack

ต่อไปนี้จะแก้ปัญหาทีละขั้นตอน ส่วนใหญ่เป็นการปรับปรุง zapcore.EncoderConfig ก่อนอื่นเราต้องเขียนไฟล์การกำหนดค่าเอง ไม่ใช้การแยกวิเคราะห์โดยตรงจากทางอย่างเป็นทางการ ก่อนอื่นสร้างไฟล์การกำหนดค่า config.yml

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"

โครงสร้างที่แมป

go
// 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 รหัสโดยละเอียดถูกละเว้น

go
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)

TimerEncoder ที่จริงแล้วเป็นฟังก์ชัน เราสามารถใช้ตัวเข้ารหัสเวลาอื่นที่ทางอย่างเป็นทางการให้ไว้ หรือเขียนเองก็ได้

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

ส่วนรวมมีดังนี้

go
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

สุดท้ายรหัสมีดังนี้

go
 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 เกี่ยวกับระดับล็อกเกอร์ ใช้ได้โดยตรง

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
   }
   // ระดับ Debug เริ่มต้น
   return zap.DebugLevel
}

สุดท้ายสร้าง

go
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     ล็อกเกอร์เริ่มต้นเสร็จสิ้น

Golang by www.golangdev.cn edit