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