戰術臥倒
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,
},
})
拋磚引玉,戰術臥倒。