Go程式設計快閃之 logrus紀錄檔庫

2023-05-24 18:00:44

戰術臥倒

golang中常見的紀錄檔包是logrus, 根據logrus的胚子和我們的生產要求,給出一個生產可用的logrus實踐姿勢。

主謂賓定狀補

logrus是一個結構化的、可插拔的、相容golang標準log api的紀錄檔庫。

快速過一下能力

  • 支援對output=TTY增加關鍵字顏色
  • 內建JSONFormatter和TextFormatter(預設)兩種Formatter
  • 支援輸出logger所在的函數行位置 log.SetReportCaller(true)
  • 可以相容golang內建的標準log庫, 建議無腦替換
  • 鼓勵輸出可解析的紀錄檔欄位,而不是大段的無法結構化的文字紀錄檔
log.WithFields(log.Fields{
 "event": event,
 "topic": topic,
 "key": key,
}).Fatal("Failed to send event")

基於現狀,湊了6個錢包上生產,下面給出一些自己的生產實踐。

添磚加瓦

1. logrus不支援捲動紀錄檔

好馬配好鞍 https://github.com/lestrrat-go/file-rotatelogs 讓你下雨天不再哭泣。

它會根據設定自動按照時間切分紀錄檔,並捲動清理紀錄檔(不用配磁碟報警,不用擔心磁碟滿故障)。

	logf, err := rotatelogs.New(
  	cfg.Log.LogDir+logName+".%Y%m%d%H%M",
  	rotatelogs.WithLinkName(cfg.Log.LogDir+logName),
  	rotatelogs.WithMaxAge(24*time.Hour),
  	rotatelogs.WithRotationTime(time.Hour),
  )
  if err != nil {
  	stdLog.Printf("failed to create rotatelogs: %s", err)
  	return
  }

2. 紀錄檔格式化

java生態預設紀錄檔輸出格式:

11:44:44.827 WARN [93ef3E0120160803114444] [main] [ClassPathXmlApplicationContext] Exception encountered during context initialization - cancelling refresh attempt

在公司中javaer佔據主流,故java的預設格式就成了公司集中式紀錄檔的"標準"格式。

很明顯,logrus預設的兩種Formatter都不匹配。

github.com/antonfisher/nested-logrus-formatter 讓你柳暗花明。

log.SetFormatter(&nested.Formatter{ // 巢狀紀錄檔相容skynet紀錄檔格式
		HideKeys:        true,
		FieldsOrder:     []string{"region", "node", "topic"},
		TimestampFormat: "2006-01-02 15:04:05.000", // 顯示ms
	})

3. 自定義Hook用法:輸出預設欄位

寫本文的時候,發現logrus官方本身支援輸出預設紀錄檔欄位。

requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Warn("something not great happened")

Hook: 通常 勾點函數用於在觸發某種事件時附帶一些動作。

logrus的Hook定義:logEntry滿足指定的logLevel紀錄檔時, 你想要做的動作(你甚至可以不設定output直接在hook輸出紀錄檔, 這就是內建write hook的實現)。

type Hook interface {
	Levels() []Level
	Fire(*Entry) error
}

範例程式碼為logLevel>=info的logEntry,固定了2個紀錄檔欄位。

type FixedFieldHook struct {
	LogLevels  []logrus.Level
	FixedField map[string]string
}

// Fire will be called when some logging function is called with current hook
// It will format log entry to string and write it to appropriate writer
func (hook *FixedFieldHook) Fire(entry *logrus.Entry) error {
	for k, v := range hook.FixedField {
		entry.Data[k] = v
	}
	return nil
}

log.AddHook(&FixedFieldHook{ // Set fixed field
		FixedField: map[string]string{"region": cfg.LocalRegion, "node": ip},
		LogLevels: []logrus.Level{
			logrus.InfoLevel,
			logrus.ErrorLevel,
			logrus.WarnLevel,
			logrus.FatalLevel,
		},
	})

拋磚引玉,戰術臥倒。