原生 log包 代码详解

131 阅读2分钟

log.Logger 结构如下:

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
}

包内对外接口如下, 实际上也是log.Logger的实例,只是这个实例是在log包内置的私有变量std

var std = New(os.Stderr, "", LstdFlags) // 包内私有变量
func Default() *Logger { return std }
func SetOutput(w io.Writer)  // 设置log.out
func SetPrefix(prefix string) // 设置log.prefix
func SetFlags(flag int) // 设置log.flag
// 以上三种调用了 l.Output(2, fmt.Sprintln(v...)
func Print(v ...interface{})
func Printf(format string, v ...interface{})
func Println(v ...interface{})
// 区别是调用了l.Output()后,会调用 os.Exit(1)
func Fatal(v ...interface{})
func Fatalf(format string, v ...interface{})
func Fatalln(v ...interface{})
// 区别是调用了l.Output()后,会调用 panic()
func Panic(v ...interface
func Panicf(format string, v ...interface{})
func Panicln(v ...interface{})

log实例的接口

// 以下三种调用了 l.Output(2, fmt.Sprintln(v...)
func (l *Logger) Printf(format string, v ...interface{})
func (l *Logger) Print(v ...interface{})
func (l *Logger) Println(v ...interface{})
// 区别是调用了l.Output()后,会调用 os.Exit(1)
func (l *Logger) Fatal(v ...interface{})
func (l *Logger) Fatalf(format string, v ...interface{})
func (l *Logger) Fatalln(v ...interface{})
// 区别是调用了l.Output()后,会调用 panic()
func (l *Logger) Panic(v ...interface{})
func (l *Logger) Panicf(format string, v ...interface{}
func (l *Logger) Panicln(v ...interface{})
func SetOutput(w io.Writer)  // 设置log.out
func SetPrefix(prefix string) // 设置log.prefix
func SetFlags(flag int) // 设置log.flag

重点是l.Output()函数的调用

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()
        
        // 判断l.flag是否符合输出runtime.Caller()
	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.buf
	l.formatHeader(&l.buf, now, file, line) // 主要是为了添加日志对应的perfix以及时间
        
        // 将message信息append到l.buf
	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) // 将l.buf内容写到l.out
	return err
}

为了添加日志对应的perfix以及时间

func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
	if l.flag&Lmsgprefix == 0 {
		*buf = append(*buf, l.prefix...)
	}
	if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
		if l.flag&LUTC != 0 {
			t = t.UTC()
		}
		if l.flag&Ldate != 0 {
			year, month, day := t.Date()
			itoa(buf, year, 4)
			*buf = append(*buf, '/')
			itoa(buf, int(month), 2)
			*buf = append(*buf, '/')
			itoa(buf, day, 2)
			*buf = append(*buf, ' ')
		}
		if l.flag&(Ltime|Lmicroseconds) != 0 {
			hour, min, sec := t.Clock()
			itoa(buf, hour, 2)
			*buf = append(*buf, ':')
			itoa(buf, min, 2)
			*buf = append(*buf, ':')
			itoa(buf, sec, 2)
			if l.flag&Lmicroseconds != 0 {
				*buf = append(*buf, '.')
				itoa(buf, t.Nanosecond()/1e3, 6)
			}
			*buf = append(*buf, ' ')
		}
	}
        
        // 对buf中写入文件、函数、行数调用信息
	if l.flag&(Lshortfile|Llongfile) != 0 {
		if l.flag&Lshortfile != 0 {
			short := file
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					short = file[i+1:]
					break
				}
			}
			file = short
		}
		*buf = append(*buf, file...)
		*buf = append(*buf, ':')
		itoa(buf, line, -1)
		*buf = append(*buf, ": "...)
	}
	if l.flag&Lmsgprefix != 0 {
		*buf = append(*buf, l.prefix...)
	}
}

测试代码

package main

import (
	"log"
	"os"
)

func main() {
	logtest("logtest")
	logtestWithStack("logtestWithStack")
}

func logtest(message string) {
	logger := log.New(os.Stdout, "test:", 1) // 最后面这个参数保证不开启runtime.Call()
	logger.Println(message)
}

func logtestWithStack(message string) {
	logger := log.New(os.Stdout, "test:", 10) // 最后面这个参数保证开启runtime.Call()
	logger.Println(message)
}

主要的区别就是打印函数的调用信息

image.png

好了,这就是官方log包的阅读笔记了