【Golang】關於Go中log的用法
一、標準日誌庫log
在日常開發中,日誌是必不可少的功能。雖然有時可以用fmt
庫輸出一些資訊,但是靈活性不夠。Go 標準庫提供了一個日誌庫log
。
1、快速使用
log
是 Go 標準庫提供的,不需要另外安裝
package main import ( "log" ) type User struct { Name string Age int } func main() { u := User{ Name: "test", Age: 18, } log.Printf("%s login, age:%d", u.Name, u.Age) log.Panicf("Oh, system error when %s login", u.Name) log.Fatalf("Danger! hacker %s login", u.Name) }
log
預設輸出到標準錯誤(stderr
),每條日誌前會自動加上日期和時間。如果日誌不是以換行符結尾的,那麼log
會自動加上換行符。即每條日誌會在新行中輸出。
log
提供了三組函式:
Print/Printf/Println
:正常輸出日誌;Panic/Panicf/Panicln
:輸出日誌後,以拼裝好的字串為引數呼叫panic
;Fatal/Fatalf/Fatalln
:輸出日誌後,呼叫os.Exit(1)
退出程式。
命名比較容易辨別,帶f
字尾的有格式化功能,帶ln
字尾的會在日誌後增加一個換行符。
注意,上面的程式中由於呼叫log.Panicf
會panic
,所以log.Fatalf
並不會呼叫
2、自定義選項
選項
Ldate
:輸出當地時區的日期,如2020/02/07
;Ltime
:輸出當地時區的時間,如11:45:45
;Lmicroseconds
:輸出的時間精確到微秒,設定了該選項就不用設定Ltime
了。如11:45:45.123123
;Llongfile
:輸出長檔名+行號,含包名,如github.com/darjun/go-daily-lib/log/flag/main.go:50
;Lshortfile
:輸出短檔名+行號,不含包名,如main.go:50
;LUTC
:如果設定了Ldate
或Ltime
,將輸出 UTC 時間,而非當地時區。
log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds) log.SetPrefix("Debug: ")
3、輸出到檔案
file := "./" + "message" + ".txt" logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766) if err != nil { panic(err) } log.SetOutput(logFile) // 將檔案設定為log輸出的檔案 log.SetPrefix("[qcpz]") log.SetFlags(log.LstdFlags | log.Lshortfile | log.Ldate | log.Ltime)
4、自定義輸出
實際上,log
庫為我們定義了一個預設的Logger
,名為std
,意為標準日誌。我們直接呼叫的log
庫的方法,其內部是呼叫std
的對應方法:
// src/log/log.go var std = New(os.Stderr, "", LstdFlags) func Printf(format string, v ...interface{}) { std.Output(2, fmt.Sprintf(format, v...)) } func Fatalf(format string, v ...interface{}) { std.Output(2, fmt.Sprintf(format, v...)) os.Exit(1) } func Panicf(format string, v ...interface{}) { s := fmt.Sprintf(format, v...) std.Output(2, s) panic(s) }
log.New
接受三個引數:
io.Writer
:日誌都會寫到這個Writer
中;prefix
:字首,也可以後面呼叫logger.SetPrefix
設定;flag
:選項,也可以後面呼叫logger.SetFlag
設定。
可以使用io.MultiWriter
實現多目的地輸出
package main import ( "bytes" "io" "log" "os" ) type User struct { Name string Age int } func main() { u := User{ Name: "test", Age: 18, } writer1 := &bytes.Buffer{} writer2 := os.Stdout writer3, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE, 0755) if err != nil { log.Fatalf("create file log.txt failed: %v", err) } logger := log.New(io.MultiWriter(writer1, writer2, writer3), "", log.Lshortfile|log.LstdFlags) logger.Printf("%s login, age:%d", u.Name, u.Age) }
二、logrus的使用
1、golang日誌庫
golang標準庫的日誌框架非常簡單,僅僅提供了print,panic和fatal三個函式對於更精細的日誌級別、日誌檔案分割以及日誌分發等方面並沒有提供支援。所以催生了很多第三方的日誌庫,但是在golang的世界裡,沒有一個日誌庫像slf4j那樣在Java中具有絕對統治地位。golang中,流行的日誌框架包括logrus、zap、zerolog、seelog等。
logrus是目前Github上star數量最多的日誌庫,目前(2018.08,下同)star數量為8119,fork數為1031。logrus功能強大,效能高效,而且具有高度靈活性,提供了自定義外掛的功能。很多開源專案,如docker,prometheus等,都是用了logrus來記錄其日誌。
zap是Uber推出的一個快速、結構化的分級日誌庫。具有強大的ad-hoc分析功能,並且具有靈活的儀表盤。zap目前在GitHub上的star數量約為4.3k。
seelog提供了靈活的非同步排程、格式化和過濾功能。
2、logrus特性
logrus具有以下特性:
- 完全相容golang標準庫日誌模組:logrus擁有六種日誌級別:debug、info、warn、error、fatal和panic,這是golang標準庫日誌模組的API的超集。如果您的專案使用標準庫日誌模組,完全可以以最低的代價遷移到logrus上。
- 可擴充套件的Hook機制:允許使用者通過hook的方式將日誌分發到任意地方,如本地檔案系統、標準輸出、logstash、elasticsearch或者mq等,或者通過hook定義日誌內容和格式等。
- 可選的日誌輸出格式:logrus內建了兩種日誌格式,JSONFormatter和TextFormatter,如果這兩個格式不滿足需求,可以自己動手實現介面Formatter,來定義自己的日誌格式。
- Field機制:logrus鼓勵通過Field機制進行精細化的、結構化的日誌記錄,而不是通過冗長的訊息來記錄日誌。
- logrus是一個可插拔的、結構化的日誌框架。
儘管 logrus有諸多優點,但是為了靈活性和可擴充套件性,官方也削減了很多實用的功能,例如:
- 沒有提供行號和檔名的支援
- 輸出到本地檔案系統沒有提供日誌分割功能
- 官方沒有提供輸出到ELK等日誌處理中心的功能
但是這些功能都可以通過自定義hook來實現。
3、日誌格式
比如,我們約定日誌格式為 Text,包含欄位如下:
請求時間
、日誌級別
、狀態碼
、執行時間
、請求IP
、請求方式
、請求路由
。
4、簡單的示例
package main import ( "os" log "github.com/sirupsen/logrus" ) func init() { // 設定日誌格式為json格式 log.SetFormatter(&log.JSONFormatter{}) // 設定將日誌輸出到標準輸出(預設的輸出為stderr,標準錯誤) // 日誌訊息輸出可以是任意的io.writer型別 log.SetOutput(os.Stdout) // 設定日誌級別為warn以上 log.SetLevel(log.WarnLevel) } func main() { log.WithFields(log.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") log.WithFields(log.Fields{ "omg": true, "number": 122, }).Warn("The group's number increased tremendously!") log.WithFields(log.Fields{ "omg": true, "number": 100, }).Fatal("The ice breaks!") }
5、Logger
logger是一種相對高階的用法, 對於一個大型專案, 往往需要一個全域性的logrus例項,即logger
物件來記錄專案所有的日誌。如
package main import ( "github.com/sirupsen/logrus" "os" ) // logrus提供了New()函式來建立一個logrus的例項。 // 專案中,可以建立任意數量的logrus例項。 var log = logrus.New() func main() { // 為當前logrus例項設定訊息的輸出,同樣地, // 可以設定logrus例項的輸出到任意io.writer log.Out = os.Stdout // 為當前logrus例項設定訊息輸出格式為json格式。 // 同樣地,也可以單獨為某個logrus例項設定日誌級別和hook,這裡不詳細敘述。 log.Formatter = &logrus.JSONFormatter{} log.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, }).Info("A group of walrus emerges from the ocean") }
6、Fields
logrus不推薦使用冗長的訊息來記錄執行資訊,它推薦使用Fields
來進行精細化的、結構化的資訊記錄。
例如下面的記錄日誌的方式:
log.Fatalf("Failed to send event %s to topic %s with key %d", event, topic, key) //替代方案 log.WithFields(log.Fields{ "event": event, "topic": topic, "key": key, }).Fatal("Failed to send event")
7、gin框架日誌中介軟體使用
package middleware import ( "fmt" "ginDemo/config" "github.com/gin-gonic/gin" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "os" "path" "time" ) // 日誌記錄到檔案 func LoggerToFile() gin.HandlerFunc { logFilePath := config.Log_FILE_PATH logFileName := config.LOG_FILE_NAME // 日誌檔案 fileName := path.Join(logFilePath, logFileName) // 寫入檔案 src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) if err != nil { fmt.Println("err", err) } // 例項化 logger := logrus.New() // 設定輸出 logger.Out = src // 設定日誌級別 logger.SetLevel(logrus.DebugLevel) // 設定 rotatelogs logWriter, err := rotatelogs.New( // 分割後的檔名稱 fileName + ".%Y%m%d.log", // 生成軟鏈,指向最新日誌檔案 rotatelogs.WithLinkName(fileName), // 設定最大儲存時間(7天) rotatelogs.WithMaxAge(7*24*time.Hour), // 設定日誌切割時間間隔(1天) rotatelogs.WithRotationTime(24*time.Hour), ) writeMap := lfshook.WriterMap{ logrus.InfoLevel: logWriter, logrus.FatalLevel: logWriter, logrus.DebugLevel: logWriter, logrus.WarnLevel: logWriter, logrus.ErrorLevel: logWriter, logrus.PanicLevel: logWriter, } lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{ TimestampFormat:"2006-01-02 15:04:05", }) // 新增 Hook logger.AddHook(lfHook) return func(c *gin.Context) { // 開始時間 startTime := time.Now() // 處理請求 c.Next() // 結束時間 endTime := time.Now() // 執行時間 latencyTime := endTime.Sub(startTime) // 請求方式 reqMethod := c.Request.Method // 請求路由 reqUri := c.Request.RequestURI // 狀態碼 statusCode := c.Writer.Status() // 請求IP clientIP := c.ClientIP() // 日誌格式 logger.WithFields(logrus.Fields{ "status_code" : statusCode, "latency_time" : latencyTime, "client_ip" : clientIP, "req_method" : reqMethod, "req_uri" : reqUri, }).Info() } } // 日誌記錄到 MongoDB func LoggerToMongo() gin.HandlerFunc { return func(c *gin.Context) { } } // 日誌記錄到 ES func LoggerToES() gin.HandlerFunc { return func(c *gin.Context) { } } // 日誌記錄到 MQ func LoggerToMQ() gin.HandlerFunc { return func(c *gin.Context) { } }
8、簡單的日誌切割
需要引入外部元件
package main import ( "time" rotatelogs "github.com/lestrrat-go/file-rotatelogs" log "github.com/sirupsen/logrus" ) func init() { path := "message.log" /* 日誌輪轉相關函式 `WithLinkName` 為最新的日誌建立軟連線 `WithRotationTime` 設定日誌分割的時間,隔多久分割一次 WithMaxAge 和 WithRotationCount二者只能設定一個 `WithMaxAge` 設定檔案清理前的最長儲存時間 `WithRotationCount` 設定檔案清理前最多儲存的個數 */ // 下面配置日誌每隔 1 分鐘輪轉一個新檔案,保留最近 3 分鐘的日誌檔案,多餘的自動清理掉。 writer, _ := rotatelogs.New( path+".%Y%m%d%H%M", rotatelogs.WithLinkName(path), rotatelogs.WithMaxAge(time.Duration(180)*time.Second), rotatelogs.WithRotationTime(time.Duration(60)*time.Second), ) log.SetOutput(writer) //log.SetFormatter(&log.JSONFormatter{}) } func main() { for { log.Info("hello, world!") time.Sleep(time.Duration(2) * time.Second) } }
- 作者:踏雪無痕
- 出處:http://www.cnblogs.com/chenpingzhao/
- 本文版權歸作者和部落格園共有,如需轉載,請聯絡pingzhao1990#163.com
如果您覺得本文對您的學習有所幫助,可通過支付寶(左) 或者 微信(右) 來打賞博主,增加博主的寫作動力