GO的日誌怎麼玩

小魔童哪吒發表於2021-08-25

上次我們們分享了 GO的定時器timer和定時任務 cron,我們們來回顧一下:

  • Timer 是什麼
  • Timer 如何使用
  • Ticker 是什麼
  • Ticker 如何使用
  • cron 是什麼
  • cron 如何使用

要是想了解如上問題的答案,歡迎檢視文章 GO的定時器Timer 和定時任務cron

今天我們們來看看 GO 的標準庫裡面的 日誌包 log

具體原始碼路徑:src/log/log.go

如何簡單使用 log 包

我們們在編輯器中看看使用log包,會有什麼提示

一看,log包裡面就涉及這些方法和資料結構,一點都不復雜,方法如上圖

我們們來用一用小案例,再來看資料結構

package main

import "log"

func main() {
   log.Println("小魔童打日誌 ... ")
   test := "Hello wrold "
   // Printf 有格式控制符
   log.Printf("%s 小魔童打日誌 ... \n", test)

   log.Fatalln("小魔童 打日誌,觸發了 Fatal")

   log.Panicln("小魔童 打日誌,觸發了 Panic")
}

執行上述程式碼,效果如下:

2021/06/xx xx:25:53 小魔童打日誌 ...
2021/06/xx xx:25:53 Hello wrold  小魔童打日誌 ...
2021/06/xx xx:25:53 小魔童 打日誌,觸發了 Fatal
exit status 1

預設可以列印出日期、時間、以及列印的內容

如何配置 log 以及相應的原理

使用 GO 裡面的 這個log包,我們們使用預設的 log 那肯定是不夠用的,例如上述小案例列印的日誌,你就不知道具體是程式碼的哪一行列印出來的,以及設定日誌列印到哪個日誌檔案裡面,等等

我們們一起來看看如何配置 log,從建立logger開始看起

新建一個 logger

我們們在基本的日誌上,加上一個字首

func main() {
  // 列印到標準輸出上
   myLog := log.New(os.Stdout, "<XMT>", log.Lshortfile|log.Ldate|log.Ltime)
   myLog.Println("小魔童列印了帶有字首的日誌 ... ")
}

執行效果如下:

<XMT>2021/06/28 12:35:47 main.go:20: 小魔童列印了帶有字首的日誌 ...

我們們看看 log.New 方法的具體實現

具體原始碼路徑:src/log/log.go

func New(out io.Writer, prefix string, flag int) *Logger {
   return &Logger{out: out, prefix: prefix, flag: flag}
}

可以看出 func New(out io.Writer, prefix string, flag int) *Logger 方法實際上是呼叫了 Logger 資料結構,我們們瞅瞅

// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
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
}

type Logger struct有上面這幾個成員,看上去每一個引數都比較好理解,根據成員名字就能夠基本知道其含義

  • mu sync.Mutex

鎖,確保原子操作

  • prefix string

每一行日誌的字首

  • out io.Writer

輸出位置,可以是檔案,可以是標準輸出

  • buf []byte

緩衝區的buffer

  • flag int

具體屬性,通過原始碼我們可以看出,具體屬性有如下幾種選擇

這些引數,都是用於控制日誌輸出的細節,例如時間,程式碼行數,字首等等

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
)

原始碼寫的註釋還是很清晰的,具體每一個欄位是做什麼的,用了之後是什麼樣的效果,根據這個註釋,一目瞭然

我們們檢視原始碼就知道,為什麼上述的小案例,日誌裡面預設就輸出了 日期、時間、具體內容,因為 log包裡面會預設 New 一個日誌,供我們預設使用

此處 var std = New(os.Stderr, "", LstdFlags) New 裡面的第三個引數需要填屬性,此處預設填了 LstdFlags

LstdFlags = Ldate | Ltime // initial values for the standard logger

LstdFlags 屬性,預設是列印日期,和時間

// Println calls l.Output to print to the logger.
// Arguments are handled in the manner of fmt.Println.
func (l *Logger) Println(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) }

(l *Logger) Println進行具體的輸出,呼叫了(l *Logger) Output

// Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
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
}

func (l *Logger) Output(calldepth int, s string) error {函式做了如下幾個事情:

  • 拼接日誌字串資料
  • 輸出到 out 中 , 此處的out 預設是標準輸出,也可以自己設定輸出到檔案

配置一個 logger

我們們用一下 log 裡面設定輸出日誌到檔案中

func main() {
   logFile, err := os.OpenFile("./XMT.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
   if err != nil {
      fmt.Println("os.OpenFile error :", err)
      return
   }
   // 設定輸出位置 ,裡面有鎖進行控制
   log.SetOutput(logFile)

   // 設定日誌屬性
   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)
   // 列印日誌
   log.Println("小魔童的 新 日誌 ... ")
   // 手動設定字首
   log.SetPrefix("【重點】")

   log.Println("小魔童的重要日誌...")
}

執行上述程式碼,效果如下:

2021/06/28 12:57:14 D:/mycode/my_new_first/my_log/main.go:36: 小魔童的 新 日誌 ... 
【重點】2021/06/28 12:57:14 D:/mycode/my_new_first/my_log/main.go:40: 小魔童的重要日誌...
  • log.SetOutput

    log.SetOutput 實際上是呼叫了 Logger 對應的 func (l *Logger) SetOutput(w io.Writer) 方法

func (l *Logger) SetOutput(w io.Writer) {
   l.mu.Lock()
   defer l.mu.Unlock()
   l.out = w
}
  • log.SetFlags

    log.SetFlags 實際上是呼叫了 Logger 對應的 SetFlags 方法

SetPrefix 也是同樣的道理

// SetFlags sets the output flags for the logger.
// The flag bits are Ldate, Ltime, and so on.
func (l *Logger) SetFlags(flag int) {
   l.mu.Lock()
   defer l.mu.Unlock()
   l.flag = flag
}

總結

  • 如何使用log
  • log 包原理和具體實現
  • 自定義日誌

歡迎點贊,關注,收藏

朋友們,寫作不易

你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡,GO的單元測試和效能測試分享

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章