go標准庫-log包源碼學習


log包是go語言提供的一個簡單的日志記錄功能,其中定義了一個結構體類型 Logger,是整個包的基礎部分,包中的其他方法都是圍繞這整個結構體創建的.

Logger結構

Logger結構的定義如下:

type Logger struct {
    mu sync.Mutex
    prefix string
    flag int
    out io.Writer
    buf []byte
}
  • mu 是sync.Mutex,它是一個同步互斥鎖,用於保證日志記錄的原子性.
  • prefix 是輸入的日志每一行的前綴
  • flag 是一個標志,用於設置日志的打印格式
  • out 日志的輸出目標,需要是一個實現了 io.Writer接口的對象,如: os.Stdout, os.Stderr, os.File等等
  • buf 用於緩存數據

與此同時還提供了一個構造方法用於創建 Logger:

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

還有圍繞Logger結構的幾個參數定義的方法:

func (l *Logger) SetOutput(w io.Writer)    // 用於設置日志輸出目標
func (l *Logger) SetPrefix(prefix string)  // 用於設置每一行日志的前綴
func (l *Logger) Prefix() string           // 獲取當前使用的前綴
func (l *Logger) SetFlags(flag int)        // 用於設置使用的輸出標志
func (l *Logger) Flags() int               // 獲取當前使用的標志

這些方法都很簡單,只是給我們提供了一個可以修改和獲取當前日志器的設置的方式.

flag可選值

在 log 包中,定義了一系列的常亮用於表示 flag,如下:

const (
	Ldate         = 1 << iota     // 1 << 0 當地時區的日期: 2009/01/23
	Ltime                         // 1 << 1 當地時區的時間: 01:23:23
	Lmicroseconds                 // 1 << 2 顯示精度到微秒: 01:23:23.123123 (應該和Ltime一起使用)
	Llongfile                     // 1 << 3 顯示完整文件路徑和行號: /a/b/c/d.go:23
	Lshortfile                    // 1 << 4 顯示當前文件名和行號: d.go:23 (如果與Llongfile一起出現,此項優先) 
	LUTC                          // 1 << 5如果設置了Ldata或者Ltime, 最好使用 UTC 時間而不是當地時區
	LstdFlags     = Ldate | Ltime // 標准日志器的初始值
)

使用方法:

  • 可以單獨使用某一個標志,此時只會顯示對應的信息
  • 可以多個合並使用,只需要將多個標志使用 | 連接即可

例如:

Ldate | Ltime   // 2017/07/31 08:01:20
Ldate | Ltime | Lmicroseconds | Llongfile   // 2017/07/31 08:01:20.123123 /a/b/c/d.go:23

常用方法

在 log 包中,定義了下面幾組方法:

func (l *Logger) Printf(format string, v ...interface{})
func (l *Logger) Print(v ...interface{})
func (l *Logger) Println(v ...interface{}) 

func (l *Logger) Fatal(v ...interface{})
func (l *Logger) Fatalf(format string, v ...interface{})
func (l *Logger) Fatalln(v ...interface{})

func (l *Logger) Panic(v ...interface{})
func (l *Logger) Panicf(format string, v ...interface{})
func (l *Logger) Panicln(v ...interface{})

即 Print*, Fatal*, Painc*, 這里方法結尾的 f 或者 ln 就跟 fmt.Print 的含義是相同的,因此上面這九個方法的使用方式其實與 fmt.Print/f/ln 是一樣的.我們直接以沒有 f 或 ln 的方法為例來看看三組方法的代碼:

func (l *Logger) Print(v ...interface{}) { 
    l.Output(2, fmt.Sprint(v...))  
}

func (l *Logger) Fatal(v ...interface{}) {
    l.Output(2, fmt.Sprint(v...))
    os.Exit(1)    
}

func (l *Logger) Panic(v ...interface{}) {
    s := fmt.Sprint(v...)
    l.Output(2, s)
    panic(s)
}

可以看到其實三個方法 都調用了接收者(也就是Logger類型的實例或指針)的 Output 方法,這個方法后面在說,其實就是字面的意思,即用來輸出我們傳入進去的字符串(fmt.Sprint方法將我們傳入的參數轉換為字符串后返回)

不同的地方在於:

  • Print 僅僅是輸出了信息
  • Fatal 不僅僅輸出了信息,還使程序停止運行
  • Painc 不僅僅輸出了信息,還調用了 panic 拋出錯誤

所以這三個方法的用處就顯而易見了.

Output方法

前面介紹了三組方法的內部都是調用了 Output 方法來實現的,也就是說實際的工作實在 Output 方法中執行的.

func (l *Logger) Output(calldepth int, s string) error {
	now := time.Now() 
	var file string
	var line int
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.flag&(Lshortfile|Llongfile) != 0 {
		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
}

這里需要提前說一下 runtime.Caller 函數,這個函數用於獲取調用Go程的棧上的函數調用所在的文件和行號信息。參數為 skip 表示我們需要獲取信息的調用層級,返回值為 程序計數器(pc), 文件名,行號以及獲取成功與否的標志。

在 Output 方法中,我們做了下面這些事情:

  1. 獲取當前事件
  2. 對 Logger實例進行加鎖操作
  3. 判斷Logger的標志位是否包含 Lshortfile 或 Llongfile, 如果包含進入步驟4, 如果不包含進入步驟5
  4. 獲取當前函數調用所在的文件和行號信息
  5. 格式化數據,並將數據寫入到 l.out 中,完成輸出
  6. 解鎖操作

這里我們注意到有一個 callpath 參數,這個參數是用於獲取某個指定層級的信息,前面3組方法中,這里使用的都是2, 這是因為,我們真正需要的文件名和行號是 調用 Print, Fatal, Panic 這些方法的地方,因此在調用 runtime.Caller 方法時,需要獲取棧中當前位置的前兩個位置處的信息.

快捷方式

log 包除了提供了上述一些需要先創建 Logger 實例才能使用的方法之外,還給我們定義了一些快捷的方法,它的實現方式也很簡單,其實就是在 log包內預先定義了一個 Logger 實例叫 std:

var std = New(os.Stderr, "", LstdFlags)

然后定義了一些可以直接使用包來調用的方法:

func Output(calldepth int, s string) error
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{})
func SetFlags(flag int)
func Flags() int
func SetOutput(w io.Writer)
func SetPrefix(prefix string)
func Prefix() string

這些方法的內部實際上大部分都是直接調用了 std 的對應的方法來實現的,不過 Print*, Panic*, Fatal* 這些方法的內部還是調用了 std.Output 方法來實現的.

前面已經涵蓋了 log 包中的所有方法,除了下面兩個:

  • func itoa(buf *[]byte, i int, wid int)
  • func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int)

這里就不細說了,主要就是用來完成數據的格式化操作的.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM