15-日志库
15 日志库
15.1 标准日志库 log
原文链接 《Go语言标准库log介绍》
Go 语言内置的 log 包实现了简单的日志服务。
15.1.1 使用 Logger
log 包定义了 Logger 类型,该类型提供了一些格式化输出的方法。本包也提供了一个预定义的“标准”logger,可以通过调用函数 Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和 Panic系列(Panic|Panicf|Panicln)来使用,比自行创建一个 logger 对象更容易使用。
例如,我们可以像下面的代码一样直接通过 log 包来调用上面提到的方法,默认会将日志信息打印到终端界面:
1 | package main |
编译并执行上面的代码会得到如下输出:
1 | 2020/09/17 08:21:23 普通的日志打印 |
- logger 会打印每条日志信息的日期、时间,默认输出到系统的终端。
- Fatal 系列函数会在写入日志信息后调用
os.Exit(1)。 - Panic系列函数会在写入日志信息后 panic。
15.1.2 配置 logger
15.1.2.1 标准 logger 的配置
默认情况下的 logger 只会提供日志的时间信息,但是很多情况下我们希望得到更多信息,比如记录该日志的文件名和行号等。log 标准库中为我们提供了定制这些设置的方法。
log 标准库中的 Flags 函数 会返回标准 logger 的输出配置,而 SetFlags函数 用来设置标准 logger 的输出配置。
1 | func Flags() int |
15.1.2.2 flag 选项
log 标准库提供了如下的 flag 选项,它们是一系列定义好的常量。
1 | const ( |
下面我们在记录日志之前先设置一下标准logger的输出选项如下:
1 | package main |
编译执行后得到的输出结果如下:
1 | 2020/09/17 08:29:05.719377 /Users/cnpeng/CnPeng/04_Demos/12_Go/oldBoy/varAndConst/const.go:9: 定制 flag 之后的日志打印 |
15.1.2.3 配置日志前缀
log标准库中还提供了关于日志信息前缀的两个方法:
1 | func Prefix() string |
其中 Prefix函数 用来查看标准 logger 的输出前缀,SetPrefix函数 用来设置输出前缀。
1 | package main |
上面的代码输出如下:
1 | 2020/09/17 08:35:08.293025 /Users/cnpeng/CnPeng/04_Demos/12_Go/oldBoy/varAndConst/const.go:9: 定制 flag 之后的日志打印 |
这样我们就能够在代码中为我们的日志信息添加指定的前缀,方便之后对日志信息进行检索和处理。
15.1.2.4 配置日志输出位置
1 | func SetOutput(w io.Writer) |
SetOutput函数 用来设置标准 logger 的输出目的地,默认是标准错误输出。
例如,下面的代码会把日志输出到同目录下的 xx.log 文件中。
1 | package main |

日志信息.log 文件中的内容如下:
1 | 2020/09/17 08:40:57.335316 /Users/cnpeng/CnPeng/04_Demos/12_Go/oldBoy/varAndConst/const.go:22: 定制 flag 之后的日志打印 |
如果要使用标准的 logger,我们通常会把上面的配置操作写到 init 函数中。
1 | func init() { |
15.1.3 创建logger
log 标准库中还提供了一个创建新 logger 对象的构造函数–New,支持我们创建自己的 logger 示例。New函数的签名如下:
1 | func New(out io.Writer, prefix string, flag int) *Logger |
New 创建一个 Logger 对象。其中,
- 参数
out设置日志信息写入的目的地。 - 参数
prefix会添加到生成的每一条日志前面。 - 参数
flag定义日志的属性(时间、文件等等)。
举个例子:
1 | package main |
将上面的代码编译执行之后,得到结果如下:
1 | [CnPeng]2020/09/17 08:48:34 const.go:10: 自定义的log对象输出日志 |
15.1.4 总结
Go 内置的 log 库功能有限,例如无法满足记录不同级别日志的情况,我们在实际的项目中根据自己的需要选择使用第三方的日志库,如 logrus、zap 等。
15.2 第三方日志库 logrus 使用
日志是程序中必不可少的一个环节,由于 Go 语言内置的日志库功能比较简洁,我们在实际开发中通常会选择使用第三方的日志库来进行开发。本文介绍了 logrus 这个日志库的基本使用。
15.2.1 logrus介绍
Logrus 是 Go(golang)的结构化 logger,与标准库 logger 的 API 完全兼容。
它有以下特点:
- 完全兼容标准日志库,拥有七种日志级别:
Trace,Debug,Info,Warning,Error,Fataland,Panic。 - 可扩展的 Hook 机制,允许使用者通过 Hook 的方式将日志分发到任意地方,如本地文件系统,
logstash,elasticsearch或者mq等,或者通过 Hook 定义日志内容和格式等 - 可选的日志输出格式,内置了两种日志格式
JSONFormater和TextFormatter,还可以自定义日志格式 Field机制,通过Filed机制进行结构化的日志记录- 线程安全
15.2.2 安装
CnPeng 不执行该安装也可以。不安装时直接在使用的地方导入,运行之后就会主动的安装。
1 | go get github.com/sirupsen/logrus |
15.2.3 基本示例
使用 Logrus 最简单的方法是简单的包级导出日志程序:
1 | package main |
运行结果如下:
1 | CnPeng:varAndConst cnpeng$ go run const.go |
15.2.4 进阶示例
对于更高级的用法,例如在同一应用程序记录到多个位置,你还可以创建 logrus Logger 的实例:
1 | package main |
上述代码运行之后,控制台的输出信息为:
1 | CnPeng:varAndConst cnpeng$ go run const.go |
另外,还会在当前 go 文件的同级目录下创建一个 logrus.log 文件,如下:

15.2.5 日志级别
15.2.5.1 日志级别
Logrus 有七个日志级别:Trace, Debug, Info, Warning, Error, Fataland ,Panic。
1 | log.Trace("Something very low level.") |
15.2.5.2 设置日志级别
你可以在 Logger 上设置日志记录级别,然后它只会记录具有该级别或以上级别任何内容的条目:
1 | // 会记录info及以上级别 (warn, error, fatal, panic) |
如果你的程序支持 debug 或环境变量模式,设置 log.Level = logrus.DebugLevel 会很有帮助。
15.2.6 字段
Logrus 鼓励通过日志字段进行谨慎的结构化日志记录,而不是冗长的、不可解析的错误消息。
例如,区别于使用 log.Fatalf("Failed to send event %s to topic %s with key %d"),你应该使用如下方式记录更容易发现的内容:
1 | log.WithFields(log.Fields{ |
WithFields 的调用是可选的。
15.2.7 默认字段
通常,将一些字段始终附加到应用程序的全部或部分的日志语句中会很有帮助。例如,你可能希望始终在请求的上下文中记录 request_id 和 user_ip。
区别于在每一行日志中写上 log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}),你可以向下面的示例代码一样创建一个 logrus.Entry 去传递这些字段。
1 | requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}) |
15.2.8 日志条目
除了使用 WithField 或 WithFields 添加的字段外,一些字段会自动添加到所有日志记录事中:
- time:记录日志时的时间戳
- msg:记录的日志信息
- level:记录的日志级别
15.2.9 Hooks
你可以添加日志级别的钩子(Hook)。例如,向异常跟踪服务发送 Error、Fatal 和Panic、信息到StatsD 或同时将日志发送到多个位置,例如 syslog。
Logrus 配有内置钩子。在 init 中添加这些内置钩子或你自定义的钩子:
1 | import ( |
注意:Syslog 钩子还支持连接到本地 syslog(例如. /dev/log or /var/run/syslog or /var/run/log)。有关详细信息,请查看 syslog hook README。
15.2.10 格式化
logrus 内置以下两种日志格式化程序:
logrus.TextFormatter、 logrus.JSONFormatter
还支持一些第三方的格式化程序,详见项目首页。
15.2.11 记录函数名
CnPeng 实际操作时,没看明白怎么得到的示例中的结果
如果你希望将调用的函数名添加为字段,请通过以下方式设置:
1 | log.SetReportCaller(true) |
这会将调用者添加为 ”method”,如下所示:
1 | {"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by", |
注意:开启这个模式会增加性能开销。
15.2.12 线程安全
默认的 logger 在并发写的时候是被 mutex 保护的,比如当同时调用 hook 和写 log 时 mutex 就会被请求,有另外一种情况,文件是以 appending mode 打开的, 此时的并发操作就是安全的,可以用logger.SetNoLock() 来关闭它。
15.2.13 gin 框架使用 logrus
Gin 是一个 go 写的 web 框架,具有高性能的优点。官方地址:https://github.com/gin-gonic/gin
CnPeng 下面的代码运行之后并没有得到想要的结果。。。 gin.log 文件中没有内容,不知道为啥
1 | // a gin with logrus demo |
15.3 自定义实现日志库
视频 81-86, 重点是视频 85。
15.3.1 需求分析
一个日志库需要支持如下内容:
- 支持向不同的地方输出
fmt.Fprintf指定内容写到哪里去path.Join( filePath, fileName)将路径和名字进行拼接,得到完整的路径os.OpenFile(fullFileName, ,)打开文件,后面两个参数为写入模式,
- 日志需要分级
- Debug
- Trace
- Info
- Waring
- Error
- Fatal
- 支持开关控制(开发接口可以打印任意级别的日志,线上环境则不允许打印日志)
- 定义 int 常量用于比较级别
- 完整的日志记录要包含有时间、行号、文件名、日志级别、日志信息
time.Now()获取时间runtime.Caller(int)获取文件信息(fileInfo)、行号、函数信息(funcInfo)。其中的 int 表示从该处上溯几层,获取的是上溯到层数的文件、行数、函数信息。- 然后通过
path.Base(fileInfo)可以得到文件名 - 通过
runtime.FuncForPc(funcInfo).NAme()可以获取包名.函数名, 然后再通过strings.Split(,)得到具体的函数名。 fmt.Sprintf(a,b...)可以将 a 和 可变长参数 b 拼接成一个字符串。
- 日志文件需要切割
- 可以按日期切割
- 可以按文件大小切割,
- 预定义一个单文件最大值,每次写日志时判断日志文件是否超出该值
- 切割时,
- 先关闭当前的日志文件——
fileOj.close() - 然后通过
os.Rename()对文件进行备份 - 打开一个新的日志文件
- 将新打开的日志文件对象作为删除目标
- 先关闭当前的日志文件——
获取文件大小的操作如下:
1 | // 获取文件大小 |
切割日志文件:
1 | // 1 将原名字 xx.log 备份为 xx.log.bak20200927084105 |
15.3.2 自定义日志库
首字母大写的标识符是对外暴露的标识符,必须要添加注释信息。









