前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Structured Logging with slog

Structured Logging with slog

作者头像
孟斯特
发布2023-10-19 16:49:16
1930
发布2023-10-19 16:49:16
举报
文章被收录于专栏:code人生code人生

原文在这里[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,还有三个其他级别的函数 —— DebugWarnError,以及一个更通用的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方法进行相同的调用:

代码语言:javascript
复制
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

代码语言:javascript
复制
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的调用产生了与上面相同的输出,但是它更快:

代码语言:javascript
复制
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方法在每个日志事件的开始时被调用,给处理器一个快速丢弃不需要的日志事件的机会。WithAttrsWithGroup方法让处理器一次格式化由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日志包,如logrgo-kit/logzap(用它的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/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-08-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟斯特 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • slog教程
  • 性能
  • 设计过程
  • 资源
    • References
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档