上次我們們分享了 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 協議》,轉載必須註明作者和本文連結