log 包實現了一個簡單的日誌功能。
Logger 結構體作爲日誌對象,生成文本行到 io.Writer。每次記錄日誌的操作都生成一行日誌,即便是log.Printf
也不用包含\n
。結構體中有sync.Metex
鎖,保證了在多 goroutine 情況時的順序寫入。
type Logger struct {
mu sync.Mutex // 確保原子寫入
prefix string // 日誌行首的前綴字符串
flag int // 屬性標誌
out io.Writer // 輸出目標
buf []byte // 字節數組緩衝區,用於加速寫入
}
其中 flags 可選項如下:
const (
Ldate = 1 << iota // 本地時區的日期: 2009/01/23
Ltime // 本地時區的時間: 01:23:23
Lmicroseconds // 毫秒粒度: 01:23:23.123123. 包含了 Ltime.
Llongfile // 完整的路徑和文件名和行號: /a/b/c/d.go:23
Lshortfile // 不包含路徑,僅文件名和行號: d.go:23. overrides Llongfile
LUTC // 使用 UTC 而非本地時區
LstdFlags = Ldate | Ltime // 標準 logger 的初始 flag 值
)
New 函數用於創建 logger,三個參數是:輸出目標, 前綴字符串,屬性標誌
func New(out io.Writer, prefix string, flag int) *Logger
logger 的 SetOutput 方法用於設置輸出目標
func (l *Logger) SetOutput(w io.Writer)
包中提供了一個標準 logger - std,我們的程序中執行log.Print
時執行的就是std.Print
var std = New(os.Stderr, "", LstdFlags)
logger 的 formatHeader 方法將前綴字符串、日期、時間、文件名和行號寫入緩衝區,它會調用itoa
函數格式化文本。
func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int)
logger 的 Output 方法打印日誌,它處理流程如下:
func (l *Logger) Output(calldepth int, s string) error
- 獲取當前時間
- logger 加鎖
- 如果 flags 設置了 Lshortfile 或 Llongfile,先解鎖,再調用
runtime.Caller()
函數獲取文件名,再加鎖。解鎖加鎖是因爲獲取文件名的動作消耗較大 - 清空 logger 的緩衝buf
- 調用
formatHeader
方法將時間、文件名、行號寫入緩衝buf - 將日誌內容追加寫入緩衝buf
- 如果行尾沒有換行符就添加一個換行符
- 將緩衝buf寫入輸出目標
logger 的 Printf、Print、Println 調用 Output 方法打印日誌,Fatal、Fatalf、Fatalln 調用 Output 方法打印日誌後以錯誤碼1退出程序,Panic、Panicf、Panicln 調用 Output 方法打印日誌後觸發panic,panic的內容就是日誌內容。
還有其他一些設置和獲取自定義 logger 或者 std logger 的前綴字符串、屬性標誌和輸出目標的函數和方法。
logger 結構體裏面的 buf 是爲了高效寫入,除此以外,log 包裏面還有一個 itoa
函數,它會高效地把整數按位轉換爲字節數組寫入緩衝buf
func itoa(buf *[]byte, i int, wid int) {
// Assemble decimal in reverse order.
var b [20]byte
bp := len(b) - 1
for i >= 10 || wid > 1 {
wid--
q := i / 10
b[bp] = byte('0' + i - q*10)
bp--
i = q
}
// i < 10
b[bp] = byte('0' + i)
*buf = append(*buf, b[bp:]...)
}
如果要像 python 的logging 那樣可以設置日誌旋轉的日誌功能可以使用這個包:lumberjack