當前位置:聯升科技 > 技術資訊 > 開發技術 >

Golang 語言的(de)标準庫 log 包怎麽使用(yòng)?

2021-01-29    作者:frank    來(lái)源:Golang語言開發棧    閱讀:
01、介紹
Golang 語言的(de)标準庫中提供了(le)一個(gè)簡單的(de) log 日志包,它不僅提供了(le)很多(duō)函數,還(hái)定義了(le)一個(gè)包含很多(duō)方法的(de)類型 Logger。但是它也(yě)有缺點,比如不支持區(qū)分(fēn)日志級别,不支持日志文件切割等。

02、函數
Golang 的(de) log 包主要提供了(le)以下(xià)幾個(gè)具備輸出功能的(de)函數:
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{}) 
這(zhè)些函數的(de)使用(yòng)方法和(hé) fmt 包完全相同,通(tōng)過查看源碼可(kě)以發現,Fatal[ln|f] 和(hé) Panic[ln|f] 實際上是調用(yòng)的(de) Print[ln|f],而 Print[ln|f] 實際上是調用(yòng)的(de) Output() 函數。
其中 Fatal[ln|f] 是調用(yòng) Print[ln|f] 之後,又調用(yòng)了(le) os.Exit(1) 退出程序。
其中 Panic[ln|f] 是調用(yòng) Panic[ln|f] 之後,又調用(yòng)了(le) panic() 函數,抛出一個(gè)恐慌。
所以,我們很有必要閱讀一下(xià) Output() 函數的(de)源碼。
函數 Output() 的(de)源碼:
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 
通(tōng)過閱讀 Output() 函數的(de)源碼,可(kě)以發現使用(yòng)互斥鎖來(lái)保證多(duō)個(gè) goroutine 寫日志的(de)安全,并且在調用(yòng) runtime.Caller() 函數之前,先釋放互斥鎖,獲取到信息後再加上互斥鎖來(lái)保證安全。
使用(yòng) formatHeader() 函數來(lái)格式化(huà)日志的(de)信息,然後保存到 buf 中,然後再把日志信息追加到 buf 的(de)末尾,然後再通(tōng)過判斷,查看日志是否爲空或末尾不是 \n,如果是就再把 \n 追加到 buf 的(de)末尾,最後将日志信息輸出。
函數 Output() 的(de)源碼也(yě)比較簡單,其中最值得(de)注意的(de)是 runtime.Caller() 函數,源碼如下(xià):
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 
通(tōng)過閱讀 runtime.Caller() 函數的(de)源碼,可(kě)以發現它接收一個(gè) int 類型的(de)參數 skip,該參數表示跳過棧幀數,log 包中的(de)輸出功能的(de)函數,使用(yòng)的(de)默認值都是 2,原因是什(shén)麽?
舉例說明(míng),比如在 main 函數中調用(yòng) log.Print,方法調用(yòng)棧爲 main->log.Print->*Logger.Output->runtime.Caller,所以此時(shí)參數 skip 的(de)值爲 2,表示 main 函數中調用(yòng) log.Print 的(de)源文件和(hé)代碼行号;
參數值爲 1,表示 log.Print 函數中調用(yòng) *Logger.Output 的(de)源文件和(hé)代碼行号;參數值爲 0,表示 *Logger.Output 函數中調用(yòng) runtime.Caller 的(de)源文件和(hé)代碼行号。
至此,我們發現 log 包的(de)輸出功能的(de)函數,全部都是把信息輸出到控制台,那麽該怎麽将信息輸出到文件中呢(ne)?
函數 SetOutPut 就是用(yòng)來(lái)設置輸出目标的(de),源碼如下(xià):
func SetOutput(w io.Writer) { 
 std.mu.Lock() 
 defer std.mu.Unlock() 
 std.out = w 
我們可(kě)以通(tōng)過函數 os.OpenFile 來(lái)打開一個(gè)用(yòng)于 I/O 的(de)文件,返回值作爲函數 SetOutput 的(de)參數。
除此之外,讀者應該還(hái)發現了(le)一個(gè)問題,輸出信息都是以日期和(hé)時(shí)間開頭,我們該怎麽記錄更加豐富的(de)信息呢(ne)?比如源文件和(hé)行号。
這(zhè)就用(yòng)到了(le)函數 SetFlags,它可(kě)以設置輸出的(de)格式,源碼如下(xià):
func SetFlags(flag int) { 
 std.SetFlags(flag) 
參數 flag 的(de)值可(kě)以是以下(xià)任意常量:
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 和(hé) Lmicroseconds 分(fēn)别表示日期、時(shí)間和(hé)微秒,需要注意的(de)是,如果設置 Lmicroseconds,那麽設置 Ltime,也(yě)不會生效。
其中 Llongfile 和(hé) Lshortfile 分(fēn)别代碼絕對(duì)路徑、源文件名、行号,和(hé)代碼相對(duì)路徑、源文件名、行号,需要注意的(de)是,如果設置 Lshortfile,那麽即使設置 Llongfile,也(yě)不會生效。
其中 LUTC 表示設置時(shí)區(qū)爲 UTC 時(shí)區(qū)。
其中 LstdFlags 表示标準記錄器的(de)初始值,包含日期和(hé)時(shí)間。
截止到現在,還(hái)缺少點東西,就是日志信息的(de)前綴,比如我們需要區(qū)分(fēn)日志信息爲 DEBUG、INFO 和(hé) ERROR。是的(de),我們還(hái)有一個(gè)函數 SetPrefix 可(kě)以實現此功能,源碼如下(xià):
func SetPrefix(prefix string) { 
 std.SetPrefix(prefix) 
函數 SetPrefix 接收一個(gè) string 類型的(de)參數,用(yòng)來(lái)設置日志信息的(de)前綴。
03、Logger
log 包定義了(le)一個(gè)包含很多(duō)方法的(de)類型 Logger。我們通(tōng)過查看輸出功能的(de)函數,發現它們都是調用(yòng) std.Output,std 是什(shén)麽?我們查看 log 包的(de)源碼。
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) 
通(tōng)過閱讀源碼,我們發現 std 實際上是 Logger 類型的(de)一個(gè)實例,Output 是 Logger 的(de)一個(gè)方法。
std 通(tōng)過 New 函數創建,參數分(fēn)别是 os.Stderr、空字符串和(hé) LstdFlags,分(fēn)别表示标準錯誤輸出、空字符串前綴和(hé)日期時(shí)間。
Logger 類型的(de)字段,注釋已經說明(míng)了(le),這(zhè)裏就不再贅述了(le)。
自定義 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 " [info] " 
 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...)) 
04、總結
本文主要介紹 Golang 語言的(de)标準庫中的(de) log 包,包括 log 包的(de)函數和(hé)自定義類型 logger 的(de)使用(yòng)方法和(hé)一些細節上的(de)注意事項。開篇也(yě)提到了(le),log 包不支持日志文件的(de)切割,我們需要自己編碼去實現,或者使用(yòng)三方庫,比如 lumberjack。在生産環境中,一般比較少用(yòng) log 包來(lái)記錄日志,通(tōng)常會使用(yòng)三方庫來(lái)記錄日志,比如 zap 和(hé) logrus 等。


相關文章(zhāng)

我們很樂(yuè)意傾聽(tīng)您的(de)聲音(yīn)!
即刻與我們取得(de)聯絡
成爲日後肩并肩合作的(de)夥伴。

行業資訊

聯系我們

13387904606

地址:新餘市仙女(nǚ)湖區(qū)仙女(nǚ)湖大(dà)道萬商紅A2棟

手機:13755589003
QQ:122322500
微信号:13755589003

江西新餘網站設計_小程序制作_OA系統開發_企業ERP管理(lǐ)系統_app開發-新餘蘇翊網絡科技有限公司 京ICP證000000号   贛公網安備 36050202000267号   

微信二維碼