Go 语言标准库中的 log 包设计简洁明了,易于上手,可以轻松记录程序运行时的信息、调试错误以及跟踪代码执行过程中的问题等。使用 log 包无需繁琐的配置即可直接使用。本文旨在深入探究 log 包的使用和原理,帮助读者更好地了解和掌握它。

使用

先来看一个 log 包的使用示例:

main.gogo run main.go

$ go run main.go
2023/03/08 22:33:22 Print
2023/03/08 22:33:22 Printf: print
2023/03/08 22:33:22 Println
2023/03/08 22:33:22 Fatal
exit status 1

以上示例代码中使用 log 包提供的 9 个函数分别对日志进行输出,最终得到 4 条打印日志。我们来分析下每个日志函数的作用,来看看为什么出现这样的结果。

log 包提供了 3 类共计 9 种方法来输出日志内容。

函数名 作用 使用示例
Print 打印日志 log.Print("Print")
Printf 打印格式化日志 log.Printf("Printf: %s", "print")
Println 打印日志并换行 log.Println("Println")
Panic 打印日志后执行 panic(s)(s 为日志内容) log.Panic("Panic")
Panicf 打印格式化日志后执行 panic(s) log.Panicf("Panicf: %s", "panic")
Panicln 打印日志并换行后执行 panic(s) log.Panicln("Panicln")
Fatal 打印日志后执行 os.Exit(1) log.Fatal("Fatal")
Fatalf 打印格式化日志后执行 os.Exit(1) log.Fatalf("Fatalf: %s", "fatal")
Fatalln 打印日志并换行后执行 os.Exit(1) log.Panicln("Panicln")
log.Fatal("Fatal")os.Exit(1)
log.Newlogger
log.Newio.Writer

使用示例:

示例输出:

[Debug] - main.go:10: custom logger

os.Stdout[Debug] - log.Lshortfile

日志属性可选项如下:

prefixLdate|Ltime
|log.Ldate|log.Ltime|log.Lshortfile
log.Newloggerlogger
方法 作用
SetOutput 设置日志输出位置
SetPrefix 设置日志输出前缀
SetFlags 设置日志属性

现在我们来看一个更加完整的使用示例:

demo.log

main.go:15: [Debug] - Print
main.go:16: [Debug] - Println

控制台输出内容如下:

[Info] - 2023/03/11 01:24:56 Print
[Info] - 2023/03/11 01:24:56 Println

demo.loglog.Lmsgprefix[Debug] - 
logger.SetXXXlogger

以上,基本涵盖了 log 包的所有常用功能。接下来我们就通过走读源码的方式来更深入的了解 log 包了。

源码

注意:本文以 Go 1.19.4 源码为例,其他版本可能存在差异。

Go 标准库的 log 包代码量非常少,算上注释也才 400+ 行,非常适合初学者阅读学习。

在上面介绍的第一个示例中,我们使用 log 包提供的 9 个公开函数对日志进行输出,并通过表格的形式分别介绍了函数的作用和使用示例,那么现在我们就来看看这几个函数是如何定义的:

std.OutputPrintXFatalos.Exit(1)Panicpanic(s)
stdOutput
std
stdNewLoggerLstdFlags
logger := log.New(os.Stdout, "[Debug] - ", log.Lshortfile)log.Print("Print")Logger
Logger
flagisDiscard
flag

具体含义我就不再一一解释了,前文的表格已经写的很详细了。

1 << iotalog.NewLoggerlog.Ldate|log.Ltime|log.Lshortfile
stdLstdFlags
isDiscardPrintXif atomic.LoadInt32(&std.isDiscard) != 0return
ioio.Discardio.Discardio.Writerio.Copy(io.Discard, io.Reader)io.Discard
NewLoggerout == io.Discardl.isDiscard1PrintXisDiscardint32bool
std.Output
Outputruntime.Caller
bufformatHeaderbuf\nPrintPrintlnPrint
l.out.Writebuf
formatHeader
formatHeader
itoaitoaintASCIIitoa
bufiyearmonthwidASCIIiitoa(&b, 12, 3)012
log.Print("Print")

stdLoggerLoggerlogger := log.New(os.Stdout, "[Debug] - ", log.Lshortfile)loggger.Printlog.PrintLogger
Logger
l.Output(2, s)calldepthruntime.Caller(calldepth)runtime.Caller
runtime.Callerskipruntime.Caller
main.go -> log.Print -> std.Output -> runtime.Callerskip
std.Outputruntime.Callerlog.Printstd.Outputmain.golog.Print

这样当代码出现问题时,就能根据日志中记录的函数调用栈来找到报错的源码位置了。

LoggerSetOutputSetPrefixSetFlags
gettersetter

当然,log 包级别的函数,也少不了这几个功能:

至此,log 包的全部代码我们就一起走读完成了。

LoggerLoggerstdLogger

使用建议

关于 log 包的使用,我还有几条建议分享给你:

DebugInfoWarnLoggerLogger

原文链接:https://jianghushinian.cn/2023/03/11/dive-into-the-go-log-standard-library/