一、Log
1.1 logger基本使用
Go語言內置的log包實現了簡單的日志服務。本包也提供了一個預定義的“標準”logger,可以通過調用函數Print系列(Print|Printf|Println)
、Fatal系列(Fatal|Fatalf|Fatalln)
、和Panic系列(Panic|Panicf|Panicln)
來使用,比自行創建一個logger對象更容易使用。
Fatal系列用于輸出一條致命錯誤信息,并調用 os.Exit(1) 終止程序運行。這個函數會在打印完錯誤信息之后立即調用 os.Exit 退出程序。
package mainimport ("log"
)func main() {log.Println("卡卡西的日志")x := "鳴人"log.Printf("%s的日志\n", x)log.Fatalln("會觸發fatal的日志")// 這里的代碼不會被執行,因為程序已經在 log.Fatalln 后退出了log.Panicln("會觸發panic的日志")
}
2024/05/16 23:03:24 卡卡西的日志
2024/05/16 23:03:24 鳴人的日志
2024/05/16 23:03:24 會觸發fatal的日志
exit status 1
1.2 logger配置
基礎的logger只提供日志的時間信息,如果需要獲取更多的信息或者輸出方式,可以通過進一步配置實現。
1.2.1 標準配置 log.SetFlags.SetFlags
log.SetFlags()
函數用于設置日志的輸出格式log.Flags()
函數來獲取當前日志包的配置標志,并將其打印輸出。
以下是 log 包中定義的一些常用選項:
- log.Ldate:日期:2009/01/23
- log.Ltime:時間:01:23:23
- log.Lmicroseconds:微秒級時間:01:23:23.123123
- log.Llongfile:文件名和行號:/a/b/c/d.go:23
- log.Lshortfile:文件名和行號:d.go:23
- log.LUTC:使用 UTC 時間
下面在記錄日志之前先設置一下logger的輸出選項:
package mainimport ("fmt""log"
)func main() {log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)log.Println("卡卡西的日志")flags := log.Flags()fmt.Println("Flags:", flags)
}
輸出結果
2024/05/16 23:25:39.519510 d:/go/練習/main.go:10: 卡卡西的日志
Flags: 13
1.2.2 前綴配置 log.SetPrefix 和 log.Prefix
log.SetPrefix
用于設置日志輸出的前綴,它接受一個字符串作為參數,這個字符串將會作為日志信息的前綴顯示在每條日志的最前面。log.Prefix
則是一個屬性,用于獲取當前日志輸出的前綴。
func main() {log.SetPrefix("復制忍者:")log.Println("卡卡西")prefix := log.Prefix()fmt.Println(prefix)
}
輸出結果是
復制忍者:2024/05/18 10:06:56 卡卡西
復制忍者:
1.2.3 輸出位置 log.SetOutput
log.SetOutput
用于設置日志輸出的目標。這個函數接受一個 io.Writer 類型的參數,將日志輸出到指定的 io.Writer 實現。
func main() {file, err := os.OpenFile("logfile.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)if err != nil {log.Fatal("Failed to open logfile.log", err)}defer file.Close()log.SetOutput(file)log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)log.Println("卡卡西")log.SetPrefix("復制忍者:")
}
1.3 logger對象創建
log.New
用于創建一個新的 Logger 對象。這個函數接受三個參數:一個實現了 io.Writer 接口的目標輸出流、一個用于添加前綴的字符串、以及一個用于指定日志屬性的標志選項。
func main() {// 創建一個新的 Logger 對象,將日志輸出到標準錯誤輸出,并添加前綴 "ERROR: "logger := log.New(os.Stderr, "ERROR: ", log.LstdFlags)// 使用新創建的 Logger 對象輸出日志logger.Println("This is an error message.")
}
輸出結果
ERROR: 2024/05/18 10:27:40 This is an error message.
二、第三方日志庫 Zap
注意:Zap并不是Go的標準庫,而是為了解決Go內置log庫功能有限的問題,所引入的第三方日志庫。在此處介紹Zap是為了方便與log庫進行對比學習。
- Zap的優點:快、結構化,分日志級別。
Zap 日志庫提供了兩種類型的日志記錄器:Sugared Logger
和 Logger
。它們分別適用于不同的日志記錄場景。
-
Sugared Logger:
- Sugared Logger 提供了結構化和格式化的日志記錄功能,支持使用類似 Printf 風格的方法記錄日志。
- 它使用了結構化的上下文字段,可以方便地記錄關鍵-值對形式的日志信息。
- Sugared Logger 適合用于一般的日志記錄需求,提供了更直觀、易用的 API。
-
Logger:
- Logger 提供了更底層的、零分配(zero-allocation)的日志記錄功能,適用于高性能、高吞吐量的日志記錄需求。
- 它的 API 相對更加簡潔,不支持結構化的上下文字段,但在性能方面有優勢。
- Logger 適合用于需要盡量減少內存分配和提升性能的場景。
根據具體的需求和場景,可以選擇使用 Zap 提供的 Sugared Logger 或 Logger 來實現相應的日志記錄功能。
2.1 Logger
- 調用
zap.NewProduction()
或者zap.NewDevelopment()
或者zap.Example()
創建了一個 Zap 日志記錄器 - 通過Logger調用Info/Error等
- 程序結束前調用 logger.Sync() 來確保所有日志都被輸出
zap.NewProduction()
:會配置 Logger 以適應生產環境的需求,例如默認會將日志輸出到標準錯誤輸出,并且會禁用堆棧跟蹤等詳細的調試信息,以減少對性能的影響。適合用于生產環境中記錄穩定運行日志的場景。
zap.NewDevelopment()
:會配置 Logger 以便于開發過程中更好地跟蹤和調試日志,例如會輸出更詳細的調試信息和堆棧跟蹤。適合用于開發和調試過程中輔助定位問題、跟蹤日志的場景。
zap.Example()
:是一個示例方法,提供了一個簡單的例子,演示了如何創建 Logger 實例、記錄不同級別的日志、以及如何添加結構化的上下文字段等操作。
下面通過簡單的http get介紹logger的用法
package mainimport ("net/http""go.uber.org/zap"
)var logger *zap.Logger// 初始化日志記錄器
func InitLogger() {logger, _ = zap.NewProduction()
}// 發送 HTTP GET 請求
func httpGet(url string){resp, err := http.Get(url)if err != nil {// 如果請求中出現錯誤,記錄錯誤日志logger.Error("Error fetching url: ",zap.String("url",url),zap.Error(err))} else {// 如果請求成功,記錄成功日志logger.Info("Success: ",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}func main() {InitLogger()// 使用了 defer 關鍵字來延遲執行 logger.Sync(),以確保在程序退出前執行同步操作defer logger.Sync()httpGet("https://blog.csdn.net/Ricardo2/article/details/134253323")httpGet("www.google.com")
}
輸出結果
{"level":"info","ts":1716001211.442937,"caller":"練習/main.go:26","msg":"Success: ","statusCode":"403 Forbidden","url":"https://blog.csdn.net/Ricardo2/article/details/134253323"}
{"level":"error","ts":1716001211.443463,"caller":"練習/main.go:20","msg":"Error fetching url: ","url":"www.google.com","error":"Get \"www.google.com\": unsupported protocol scheme \"\"","stacktrace":"main.httpGet\n\td:/go/練習/main.go:20\nmain.main\n\td:/go/練習/main.go:41\nruntime.main\n\tE:/goland/src/runtime/proc.go:250"}
2.2 SugaredLogger
大部分的實現基本都相同。
惟一的區別是,我們通過logger.Sugar()方法來獲取一個SugaredLogger。
package mainimport ("net/http""go.uber.org/zap"
)var sugarLogger *zap.SugaredLogger// 初始化日志記錄器
func InitLogger() {logger, _ := zap.NewProduction()sugarLogger = logger.Sugar()
}// 發送 HTTP GET 請求
func httpGet(url string){sugarLogger.Debugf("Trying to grt request for %s", url)resp, err := http.Get(url)if err != nil {// 如果請求中出現錯誤,記錄錯誤日志sugarLogger.Error("Error fetching url: ",zap.String("url",url),zap.Error(err))} else {// 如果請求成功,記錄成功日志sugarLogger.Info("Success: ",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}func main() {InitLogger()// 使用了 defer 關鍵字來延遲執行 logger.Sync(),以確保在程序退出前執行同步操作defer sugarLogger.Sync()httpGet("https://blog.csdn.net/Ricardo2/article/details/134253323")httpGet("www.google.com")
}
輸出結果
{"level":"info","ts":1716001865.691326,"caller":"練習/main.go:28","msg":"Success: {statusCode 15 0 403 Forbidden <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}"}
{"level":"error","ts":1716001865.6924412,"caller":"練習/main.go:22","msg":"Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0 Get \"www.google.com\": unsupported protocol scheme \"\"}","stacktrace":"main.httpGet\n\td:/go/練習/main.go:22\nmain.main\n\td:/go/練習/main.go:43\nruntime.main\n\tE:/goland/src/runtime/proc.go:250"}
2.3 日志級別
Zap 日志庫支持以下幾種日志級別,可以根據不同的需求來選擇合適的級別記錄日志:
- Debug:
用于記錄調試過程中的詳細信息,通常在開發和調試階段使用。
使用 logger.Debug() 方法記錄 Debug 級別的日志。 - Info:
用于記錄程序運行過程中的一般信息,例如啟動信息、關鍵事件等。
使用 logger.Info() 方法記錄 Info 級別的日志。 - Warn:
用于記錄可能出現問題但不會影響程序正常運行的警告信息,例如參數使用不當、潛在的問題等。
使用 logger.Warn() 方法記錄 Warn 級別的日志。 - Error:
用于記錄程序中的錯誤信息,例如異常、錯誤狀態等。
使用 logger.Error() 方法記錄 Error 級別的日志。 - DPanic:
用于記錄嚴重的錯誤,會導致程序進入恐慌狀態的錯誤。
使用 logger.DPanic() 方法記錄 DPanic 級別的日志。 - Panic:
用于記錄導致程序無法繼續正常運行的錯誤,記錄后會觸發程序 panic。
使用 logger.Panic() 方法記錄 Panic 級別的日志。 - Fatal:
用于記錄導致程序無法繼續運行的嚴重錯誤,記錄后會觸發程序退出。
使用 logger.Fatal() 方法記錄 Fatal 級別的日志。
2.4 Zap配置
2.4.1 標準配置
下面介紹如何對Zap的日志做詳細的配置:
- 如何寫入日志
- 日志寫入到哪里
- 寫入什么級別的日志
具體來說,將使用zap.New(…)
方法來手動傳遞所有配置
func New(core zapcore.Core, options ...Option) *Logger
zapcore.Core 接口類型的實例,定義了日志記錄的核心功能,包括日志級別的判斷LogLevel、格式化日志消息Encoder、輸出日志的目的地WriteSyncer等。
(1)Encoder:編碼器(如何寫入日志)。我們將使用開箱即用的NewJSONEncoder()
,并使用預先設置的ProductionEncoderConfig()
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
(2)WriterSyncer :指定日志將寫到哪里去。我們使用zapcore.AddSync()
函數并且將打開的文件句柄傳進去。通過使用 AddSync
函數,可以將一個標準的 Go io.Writer
實例(比如文件、標準輸出等)包裝成一個符合 Zap 日志庫要求的 WriteSyncer
實例
file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)
(3)Log Level:哪種級別的日志將被寫入。
下面將修改上述部分中的Logger代碼,主要是重寫InitLogger()
方法。
package mainimport ("net/http""os""go.uber.org/zap""go.uber.org/zap/zapcore"
)var sugarLogger *zap.SugaredLoggerfunc getEncoder() zapcore.Encoder {return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}func getLogWriter() zapcore.WriteSyncer {file, _ := os.Create("./test.log")return zapcore.AddSync(file)
}// 初始化日志記錄器
func InitLogger() {writeSyncer := getLogWriter()encoder := getEncoder()core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)logger := zap.New(core)sugarLogger = logger.Sugar()
}// 發送 HTTP GET 請求
func httpGet(url string) {sugarLogger.Debugf("Trying to grt request for %s", url)resp, err := http.Get(url)if err != nil {// 如果請求中出現錯誤,記錄錯誤日志sugarLogger.Error("Error fetching url: ",zap.String("url", url),zap.Error(err))} else {// 如果請求成功,記錄成功日志sugarLogger.Info("Success: ",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}func main() {InitLogger()// 使用了 defer 關鍵字來延遲執行 logger.Sync(),以確保在程序退出前執行同步操作defer sugarLogger.Sync()httpGet("https://blog.csdn.net/Ricardo2/article/details/134253323")httpGet("www.google.com")
}
結果是
{"level":"debug","ts":1716607412.904807,"msg":"Trying to grt request for https://blog.csdn.net/Ricardo2/article/details/134253323"}
{"level":"info","ts":1716607415.0869148,"msg":"Success: {statusCode 15 0 200 OK <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}"}
{"level":"debug","ts":1716607415.0869148,"msg":"Trying to grt request for www.google.com"}
{"level":"error","ts":1716607415.0869148,"msg":"Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0 Get \"www.google.com\": unsupported protocol scheme \"\"}"}
2.4.2 修改輸出格式
NewJSONEncoder()
創建的編碼器將日志事件格式化為 JSON 格式的字符串。這種格式在日志收集系統、日志分析工具等場景中通常更易于處理和解析。
NewConsoleEncoder()
創建的編碼器將日志事件格式化為人類可讀的文本格式,通常采用一種類似于控制臺輸出的格式。這種格式適合在終端中查看日志。
func getEncoder() zapcore.Encoder {return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
}
結果是
1.7166081613816104e+09 debug Trying to grt request for https://blog.csdn.net/Ricardo2/article/details/134253323
1.7166081620810077e+09 info Success: {statusCode 15 0 200 OK <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}
1.7166081620810077e+09 debug Trying to grt request for www.google.com
1.7166081620815365e+09 error Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0 Get "www.google.com": unsupported protocol scheme ""}
2.4.3 修改時間展示方式
首先要覆蓋Encoder終默認的ProductionConfig(),進行手動配置:
- 修改時間編碼器
- 在日志文件中使用大寫字母記錄日志級別
func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderreturn zapcore.NewConsoleEncoder(encoderConfig)
}
2.4.4 增加調用者函數的信息
將在zap.New(..)
函數中添加一個Option。
logger := zap.New(core, zap.AddCaller())
最后,結果是
2024-05-25T11:41:42.283+0800 DEBUG 練習/main.go:37 Trying to grt request for https://blog.csdn.net/Ricardo2/article/details/134253323
2024-05-25T11:41:43.196+0800 INFO 練習/main.go:47 Success: {statusCode 15 0 200 OK <nil>} {url 15 0 https://blog.csdn.net/Ricardo2/article/details/134253323 <nil>}
2024-05-25T11:41:43.199+0800 DEBUG 練習/main.go:37 Trying to grt request for www.google.com
2024-05-25T11:41:43.199+0800 ERROR 練習/main.go:41 Error fetching url: {url 15 0 www.google.com <nil>} {error 26 0 Get "www.google.com": unsupported protocol scheme ""}
2.4.5 將日志同時輸出到文件和終端
func getLogWriter() zapcore.WriteSyncer {file, _ := os.Create("./test.log")// 利用io.MultiWriter支持文件和終端兩個輸出目標ws := io.MultiWriter(file, os.Stdout)return zapcore.AddSync(ws)
}
2.4.6 將err日志單獨輸出到文件
將ERROR級別的日志單獨輸出到一個名為xx.err.log的日志文件中。
func InitLogger() {encoder := getEncoder()// test.log記錄全量日志logF, _ := os.Create("./test.log")c1 := zapcore.NewCore(encoder, zapcore.AddSync(logF), zapcore.DebugLevel)// test.err.log記錄ERROR級別的日志errF, _ := os.Create("./test.err.log")c2 := zapcore.NewCore(encoder, zapcore.AddSync(errF), zap.ErrorLevel)// 使用NewTee將c1和c2合并到corecore := zapcore.NewTee(c1, c2)logger = zap.New(core, zap.AddCaller())
}