原文在这里[1]。
由 Jonathan Amsterdam 发布于 22 August 2023
Go 1.21中的新的log/slog包为标准库带来了结构化日志。结构化日志使用键值对,因此可以快速可靠地进行解析、过滤、搜索和分析。对于服务器来说,日志是开发人员观察系统详细行为的重要方式,通常也是他们首先进行调试的地方。因此,日志往往很多,快速搜索和过滤它们的能力是必不可少的。
标准库自Go首次发布以来就有一个日志包,即log。随着时间的推移,我们了解到结构化日志对Go程序员来说很重要。它在我们的年度调查中一直排名靠前,Go生态系统中的许多包都提供了它。其中一些非常受欢迎:Go的第一个结构化日志包之一,logrus[2],被超过100,000个其他包使用。
有许多结构化日志包可供选择,大型程序通常会通过它们的依赖关系包含多个。主程序可能需要配置每个这些日志包,以便日志输出一致:它们都发送到同一个地方,以相同的格式。通过在标准库中包含结构化日志,我们可以提供一个所有其他结构化日志包都可以共享的公共框架。
slog教程
下面是一个使用slog的简单程序:
代码语言:javascript复制package main
import "log/slog"
func main() {
slog.Info("hello, world")
}
程序执行后会输出:
代码语言:javascript复制2023/08/04 16:09:19 INFO hello, world
Info
函数使用默认的记录器在Info日志级别打印一条消息,这个记录器在这种情况下是来自log包的默认记录器 —— 当你写log.Printf
时得到的就是这个记录器。这就解释了为什么输出看起来如此相似:只有“INFO”是新的。开箱即用,slog和原始的log包一起工作,使得开始变得容易。
除了Info
,还有三个其他级别的函数 —— Debug
、Warn
和Error
,以及一个更通用的Log
函数,该函数将级别作为参数。在slog中,级别只是整数,所以你不受四个命名级别的限制。例如,
Info是零,Warn是4
,所以如果你的日志系统有一个在这两者之间的级别,你可以为它使用2。
与log包不同,我们可以通过在消息后面写入它们来轻松添加键值对到我们的输出:
代码语言:javascript复制slog.Info("hello, world", "user", os.Getenv("USER"))
现在的输出看起来像这样:
代码语言:javascript复制2023/08/04 16:27:19 INFO hello, world user=jba
如我们所述,slog的顶级函数使用默认的记录器。我们可以明确地获取这个记录器,并调用它的方法:
代码语言:javascript复制logger := slog.Default()
logger.Info("hello, world", "user", os.Getenv("USER"))
每个顶级函数对应于slog.Logger上的一个方法。输出与之前相同。
最初,slog的输出通过默认的log.Logger进行,产生我们上面看到的输出。我们可以通过更改记录器使用的处理器来更改输出。slog带有两个内置的处理器。TextHandler以key=value的形式发出所有日志信息。这个程序使用TextHandler
创建一个新的记录器,并对Info方法进行相同的调用:
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
logger.Info("hello, world", "user", os.Getenv("USER"))
现在的输出看起来像这样:
代码语言:javascript复制time=2023-08-04T16:56:03.786-04:00 level=INFO msg="hello, world" user=jba
所有内容都已转换为键值对,根据需要引用字符串以保留结构。
对于JSON输出,使用内置的JSONHandler
:
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("hello, world", "user", os.Getenv("USER"))
现在我们的输出是一系列JSON对象,每个日志调用一个:
代码语言:javascript复制{"time":"2023-08-04T16:58:02.939245411-04:00","level":"INFO","msg":"hello, world","user":"jba"}
你不仅限于内置的处理器。任何人都可以通过实现slog.Handler
接口来编写一个处理器。处理器可以以特定的格式生成输出,或者可以包装另一个处理器以添加功能。slog文档中的一个例子显示了如何编写一个包装处理器,该处理器改变了将显示日志消息的最小级别。
到目前为止,我们一直使用的交替的键值语法对于属性来说是方便的,但对于频繁执行的日志语句,使用Attr
类型并调用LogAttrs
方法可能更有效。这些一起工作以最小化内存分配。有一些函数可以从字符串、数字和其他常见类型构建Attrs。这个对LogAttrs的调用产生了与上面相同的输出,但是它更快:
slog.LogAttrs(context.Background(), slog.LevelInfo, "hello, world",
slog.String("user", os.Getenv("USER")))
slog还有很多内容:
•如LogAttrs的调用所示,你可以将context.Context传递给一些日志函数,以便处理器可以提取上下文信息,如跟踪ID。(取消上下文并不会阻止日志条目被写入。)•你可以调用Logger.With来向记录器添加将出现在其所有输出中的属性,有效地提取出几个日志语句的公共部分。这不仅方便,而且可以帮助提高性能,如下面所讨论的。•属性可以组合成组。这可以为你的日志输出添加更多的结构,并可以帮助消除那些否则会相同的键的歧义。•你可以通过为其类型提供LogValue方法来控制值在日志中的显示方式。这可以用来将结构的字段作为一组[3]记录,或者删除敏感数据[4],等等。
了解slog的所有内容的最好地方是这里[5]。
性能
我们希望slog能快。为了获得大规模的性能提升,我们设计了Handler接口[6]以提供优化机会。Enabled
方法在每个日志事件的开始时被调用,给处理器一个快速丢弃不需要的日志事件的机会。WithAttrs
和WithGroup
方法让处理器一次格式化由Logger.With
添加的属性,而不是在每次日志调用时。当大的属性,如http.Request
,被添加到Logger然后在许多日志调用中使用时,这种预格式化可以提供显著的加速。
为了指导我们的性能优化工作,我们研究了现有开源项目中的日志记录的典型模式。我们发现超过95%的日志方法调用传递五个或更少的属性。我们还对属性的类型进行了分类,发现少数几种常见类型占了大多数。然后我们编写了捕获常见情况的基准测试,并用它们作为指南来看时间去了哪里。最大的收益来自对内存分配的细心关注。
设计过程
slog包是自2012年Go 1发布以来对标准库的最大的增加之一。我们希望花时间设计它,我们知道社区反馈是必不可少的。
到2022年4月,我们已经收集了足够的数据来证明结构化日志对Go社区的重要性。Go团队决定探索将其添加到标准库。
我们开始研究现有的结构化日志包是如何设计的。我们还利用存储在Go模块代理上的大量开源Go代码,了解这些包实际上是如何使用的。我们的第一个设计是由这项研究以及Go的简单性精神所启发的。我们希望一个在页面上轻便且易于理解的API,而不牺牲性能。
我们从来没有目标是要替换现有的第三方日志包。它们都很擅长自己的工作,替换现有的工作良好的代码很少是开发人员时间的好用途。我们将API分为一个前端,Logger,它调用一个后端接口,Handler。这样,现有的日志包可以与一个公共的后端进行通信,因此使用它们的包可以在不需要重写的情况下进行互操作。许多常见的日志包,包括Zap[7]、logr[8]和hclog[9],都已经编写或正在进行处理器。
我们在Go团队和其他有广泛日志经验的开发人员中分享了我们的初步设计。我们根据他们的反馈做了修改,到2022年8月,我们觉得我们有了一个可行的设计。8月29日,我们公开了我们的实验性实现[10],并开始了GitHub讨论[11],以听取社区的意见。反应热烈且大部分是积极的。感谢其他结构化日志包的设计者和用户的深思熟虑的评论,我们做了几个改变,并添加了一些功能,如组和LogValuer接口。我们两次改变了日志级别到整数的映射。
经过两个月和大约300条评论,我们觉得我们准备好了一个实际的提案[12]和相应的设计文档[13]。提案问题引起了超过800条评论,并对API和实现进行了许多改进。以下是两个API更改的例子,都涉及到context.Context
:
1.最初,API支持将记录器添加到上下文中。许多人觉得这是一种方便的方式,可以轻松地将记录器通过不关心它的代码级别。但其他人觉得这是在走私一个隐式的依赖,使代码更难理解。最终,我们因为太有争议而删除了这个功能。2.我们还对传递一个上下文到日志方法的相关问题进行了争论,尝试了许多设计。我们最初抵制将上下文作为第一个参数传递的标准模式,因为我们不希望每个日志调用都需要一个上下文,但最终创建了两组日志方法,一组带有上下文,一组没有。
我们没有做的一个改变涉及到表示属性的交替键和值语法:
代码语言:javascript复制slog.Info("message", "k1", v1, "k2", v2)
许多人强烈地认为这是一个坏主意。他们发现它很难阅读,并且很容易通过省略一个键或值来弄错。他们更喜欢明确的属性来表示结构:
代码语言:javascript复制slog.Info("message", slog.Int("k1", v1), slog.String("k2", v2))
但我们觉得轻量级的语法对于保持Go易用和有趣,特别是对于新的Go程序员来说很重要。我们也知道几个Go日志包,如logr
、go-kit/log
和zap(用它的SugaredLogger)
成功地使用了交替的键和值。我们添加了一个vet检查[14]来捕获常见的错误,但没有改变设计。
2023年3月15日,提案被接受,但还有一些小的未解决的问题。在接下来的几周里,提出并解决了十个额外的变更。到7月初,log/slog包的实现完成了,测试/slogtest包用于验证处理器和vet检查用于正确使用交替的键和值。
8月8日,Go 1.21发布了,slog也随之发布。我们希望你发现它有用,使用起来和构建一样有趣。
感谢所有参与讨论和提案过程的人。你们的贡献极大地改进了slog。
资源
log/slog包的文档[15]解释了如何使用它,并提供了几个例子。
wiki页面[16]有Go社区提供的额外资源,包括各种处理器。
如果你想写一个处理器,请参考处理器编写指南[17]。
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)[18]进行许可,使用时请注明出处。 Author: mengbin[19] blog: mengbin[20] Github: mengbin92[21] cnblogs: 恋水无意[22]
References
[1]
这里: https://go.dev/blog/slog
[2]
logrus: https://pkg.go.dev/github.com/sirupsen/logrus
[3]
结构的字段作为一组: https://pkg.go.dev/log/slog@master#example-LogValuer-Group
[4]
删除敏感数据: https://pkg.go.dev/log/slog@master#example-LogValuer-Secret
[5]
这里: https://pkg.go.dev/log/slog
[6]
Handler接口: https://pkg.go.dev/log/slog#Handler
[7]
Zap: https://github.com/uber-go/zap/tree/master/exp/zapslog
[8]
logr: https://github.com/go-logr/logr/pull/196
[9]
hclog: https://github.com/evanphx/go-hclog-slog
[10]
实验性实现: https://github.com/golang/exp/tree/master/slog
[11]
GitHub讨论: https://github.com/golang/go/discussions/54763
[12]
实际的提案: https://go.dev/issue/56345
[13]
设计文档: https://go.googlesource.com/proposal/ /03441cb358c7b27a8443bca839e5d7a314677ea6/design/56345-structured-logging.md
[14]
vet检查: https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog
[15]
文档: https://pkg.go.dev/log/slog
[16]
wiki页面: https://github.com/golang/go/wiki/Resources-for-slog
[17]
处理器编写指南: https://github.com/golang/example/blob/master/slog-handler-guide/README.md
[18]
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0): https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
[19]
mengbin: mengbin1992@outlook.com
[20]
mengbin: https://mengbin.top
[21]
mengbin92: https://mengbin92.github.io/
[22]
恋水无意: https://www.cnblogs.com/lianshuiwuyi/