log包在Golang語言的標準庫中是怎麼使用的?
導讀 | Golang 語言的標準庫中提供了一個簡單的 log 日誌包,它不僅提供了很多函式,還定義了一個包含很多方法的型別 Logger。但是它也有缺點,比如不支援區分日誌級別,不支援日誌檔案切割等。 |
Golang 語言的標準庫中提供了一個簡單的 log 日誌包,它不僅提供了很多函式,還定義了一個包含很多方法的型別 Logger。但是它也有缺點,比如不支援區分日誌級別,不支援日誌檔案切割等。
Golang 的 log 包主要提供了以下幾個具備輸出功能的函式:
func Fatal(v ...interface{}) func Fatalf(format string, v ...interface{}) func Fatalln(v ...interface{}) func Panic(v ...interface{}) func Panicf(format string, v ...interface{}) func Panicln(v ...interface{}) func Print(v ...interface{}) func Printf(format string, v ...interface{}) func Println(v ...interface{})
這些函式的使用方法和 fmt 包完全相同,透過檢視原始碼可以發現,Fatal[ln|f] 和 Panic[ln|f] 實際上是呼叫的 Print[ln|f],而 Print[ln|f] 實際上是呼叫的 Output() 函式。
其中 Fatal[ln|f] 是呼叫 Print[ln|f] 之後,又呼叫了 os.Exit(1) 退出程式。
其中 Panic[ln|f] 是呼叫 Panic[ln|f] 之後,又呼叫了 panic() 函式,丟擲一個恐慌。
所以,我們很有必要閱讀一下 Output() 函式的原始碼。
函式 Output() 的原始碼:
func (l *Logger) Output(calldepth int, s string) error { now := time.Now() // get this early. var file string var line int l.mu.Lock() defer l.mu.Unlock() if l.flag&(Lshortfile|Llongfile) != 0 { // Release lock while getting caller info - it's expensive. l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } l.mu.Lock() } l.buf = l.buf[:0] l.formatHeader(&l.buf, now, file, line) l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } _, err := l.out.Write(l.buf) return err }
透過閱讀 Output() 函式的原始碼,可以發現使用互斥鎖來保證多個 goroutine 寫日誌的安全,並且在呼叫 runtime.Caller() 函式之前,先釋放互斥鎖,獲取到資訊後再加上互斥鎖來保證安全。
使用 formatHeader() 函式來格式化日誌的資訊,然後儲存到 buf 中,然後再把日誌資訊追加到 buf 的末尾,然後再透過判斷,檢視日誌是否為空或末尾不是 \n,如果是就再把 \n 追加到 buf 的末尾,最後將日誌資訊輸出。
函式 Output() 的原始碼也比較簡單,其中最值得注意的是 runtime.Caller() 函式,原始碼如下:
func Caller(skip int) (pc uintptr, file string, line int, ok bool) { rpc := make([]uintptr, 1) n := callers(skip+1, rpc[:]) if n < 1 { return } frame, _ := CallersFrames(rpc).Next() return frame.PC, frame.File, frame.Line, frame.PC != 0 }
透過閱讀 runtime.Caller() 函式的原始碼,可以發現它接收一個 int 型別的引數 skip,該參數列示跳過棧幀數,log 包中的輸出功能的函式,使用的預設值都是 2,原因是什麼?
舉例說明,比如在 main 函式中呼叫 log.Print,方法呼叫棧為 main->log.Print->*Logger.Output->runtime.Caller,所以此時引數 skip 的值為 2,表示 main 函式中呼叫 log.Print 的原始檔和程式碼行號;
引數值為 1,表示 log.Print 函式中呼叫 *Logger.Output 的原始檔和程式碼行號;引數值為 0,表示 *Logger.Output 函式中呼叫 runtime.Caller 的原始檔和程式碼行號。
至此,我們發現 log 包的輸出功能的函式,全部都是把資訊輸出到控制檯,那麼該怎麼將資訊輸出到檔案中呢?
函式 SetOutPut 就是用來設定輸出目標的,原始碼如下:
func SetOutput(w io.Writer) { std.mu.Lock() defer std.mu.Unlock() std.out = w }
我們可以透過函式 os.OpenFile 來開啟一個用於 I/O 的檔案,返回值作為函式 SetOutput 的引數。
除此之外,讀者應該還發現了一個問題,輸出資訊都是以日期和時間開頭,我們該怎麼記錄更加豐富的資訊呢?比如原始檔和行號。
這就用到了函式 SetFlags,它可以設定輸出的格式,原始碼如下:
func SetFlags(flag int) { std.SetFlags(flag) }
引數 flag 的值可以是以下任意常量:
const ( Ldate = 1 << iota // the date in the local time zone: 2009/01/23 Ltime // the time in the local time zone: 01:23:23 Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. Llongfile // full file name and line number: /a/b/c/d.go:23 Lshortfile // final file name element and line number: d.go:23. overrides Llongfile LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone Lmsgprefix // move the "prefix" from the beginning of the line to before the message LstdFlags = Ldate | Ltime // initial values for the standard logger )
其中 Ldate、Ltime 和 Lmicroseconds 分別表示日期、時間和微秒,需要注意的是,如果設定 Lmicroseconds,那麼設定 Ltime,也不會生效。
其中 Llongfile 和 Lshortfile 分別程式碼絕對路徑、原始檔名、行號,和程式碼相對路徑、原始檔名、行號,需要注意的是,如果設定 Lshortfile,那麼即使設定 Llongfile,也不會生效。
其中 LUTC 表示設定時區為 UTC 時區。
其中 LstdFlags 表示標準記錄器的初始值,包含日期和時間。
截止到現在,還缺少點東西,就是日誌資訊的字首,比如我們需要區分日誌資訊為 DEBUG、INFO 和 ERROR。是的,我們還有一個函式 SetPrefix 可以實現此功能,原始碼如下:
func SetPrefix(prefix string) { std.SetPrefix(prefix) }
函式 SetPrefix 接收一個 string 型別的引數,用來設定日誌資訊的字首。
log 包定義了一個包含很多方法的型別 Logger。我們透過檢視輸出功能的函式,發現它們都是呼叫 std.Output,std 是什麼?我們檢視 log 包的原始碼。
type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix on each line to identify the logger (but see Lmsgprefix) flag int // properties out io.Writer // destination for output buf []byte // for accumulating text to write } func New(out io.Writer, prefix string, flag int) *Logger { return &Logger{out: out, prefix: prefix, flag: flag} } var std = New(os.Stderr, "", LstdFlags)
透過閱讀原始碼,我們發現 std 實際上是 Logger 型別的一個例項,Output 是 Logger 的一個方法。
std 透過 New 函式建立,引數分別是 os.Stderr、空字串和 LstdFlags,分別表示標準錯誤輸出、空字串字首和日期時間。
Logger 型別的欄位,註釋已經說明了,這裡就不再贅述了。
自定義 Logger:
func main () { logFile, err := os.OpenFile("error1.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) if err != nil { fmt.Println(err) return } defer logFile.Close() logs := DefinesLogger(logFile, "", log.LstdFlags|log.Lshortfile) logs.Debug("message") logs.Debugf("%s", "content") } // 自定義 logger type Logger struct { definesLogger *log.Logger } type Level int8 const( LevelDebug Level = iota LevelInfo LevelError ) func (l Level) String() string { switch l { case LevelDebug: return " [debug] " case LevelInfo: return " " case LevelError: return " [error] " } return "" } func DefinesLogger(w io.Writer, prefix string, flag int) *Logger { l := log.New(w, prefix, flag) return &Logger{definesLogger: l} } func (l *Logger) Debug(v ...interface{}) { l.definesLogger.Print(LevelDebug, fmt.Sprint(v...)) } func (l *Logger) Debugf(format string, v ...interface{}) { l.definesLogger.Print(LevelDebug, fmt.Sprintf(format, v...)) } func (l *Logger) Info(v ...interface{}) { l.definesLogger.Print(LevelInfo, fmt.Sprint(v...)) } func (l *Logger) Infof(format string, v ...interface{}) { l.definesLogger.Print(LevelInfo, fmt.Sprintf(format, v...)) } func (l *Logger) Error(v ...interface{}) { l.definesLogger.Print(LevelError, fmt.Sprint(v...)) } func (l *Logger) Errorf(format string, v ...interface{}) { l.definesLogger.Print(LevelError, fmt.Sprintf(format, v...)) }
本文主要介紹 Golang 語言的標準庫中的 log 包,包括 log 包的函式和自定義型別 logger 的使用方法和一些細節上的注意事項。開篇也提到了,log 包不支援日誌檔案的切割,我們需要自己編碼去實現,或者使用三方庫,比如 lumberjack。在生產環境中,一般比較少用 log 包來記錄日誌,通常會使用三方庫來記錄日誌,比如 zap 和 logrus 等。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2757221/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- go語言標準庫 - logGo
- Golang語言標準庫time實戰篇Golang
- Golang中的unsafe標準庫包Golang
- - C語言標準庫C語言
- go語言標準庫 - timeGo
- golang中的log庫Golang
- go語言標準庫 - strconvGo
- C語言標準函式庫C語言函式
- go語言標準庫 - regexpGo
- golang標準庫的分析os包(6)Golang
- 程式語言的六個標準
- golang標準庫之 fmtGolang
- Go語言學習(1)——標準庫fmtGo
- 標準庫unsafe:帶你突破golang中的型別限制Golang型別
- 語言是 Go 還是 Golang?Golang
- C語言的本質(22)——C標準庫之字串操作C語言字串
- 標準c語言03C語言
- 標準C語言4C語言
- 標準C語言5C語言
- 標準C語言1C語言
- 標準C語言2C語言
- 什麼是 C 和 C ++ 標準庫?
- CUJ:高效使用標準庫:set的iterator是mutable的還是immutable的? (轉)
- Golang語言中的method是什麼Golang
- CUJ:標準庫:標準庫中的搜尋演算法 (轉)演算法
- 資料庫 - 關聯式資料庫標準語言SQL資料庫SQL
- CUJ:高效使用標準庫:STL中的unary predicate (轉)
- 關於 Unity 遊戲的效能引數的標準是怎麼制定的?Unity遊戲
- 看看牛人們是怎麼評價程式語言的
- 年度語言 golang 使用感受Golang
- 軟體測試的准入準出是什麼?標準是什麼?
- 標準庫 fmt 包的基本使用
- 【Golang】golang中那些不需要傳遞引數就能使用的變數是怎麼回事Golang變數
- Golang標準庫學習—container/heapGolangAI
- Golang語言中的interface是什麼(上)Golang
- Golang語言中的interface是什麼(下)Golang
- 手寫程式語言-如何為 GScript 編寫標準庫
- 為什麼《七週七語言》選中的是這幾種語言?