Go语言结构化日志slog的用法解析
前言
go 1.21.0
版本引入了一个新的包 log/slog
,该包提供了结构化日志的功能。相比于普通的日志,结构化日志更受欢迎,因为它具有更高的可读性,并且在处理、分析和搜索等方面具有显著的优势。
接下来让我们深入探讨 log/slog
包的使用,准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
slog 包
slog
包提供了结构化日志,其中的日志记录包含了 消息、严重级别 以及 各种其他属性,这些属性以 键值对 的形式表示。
slog
包的主要功能如下所示:
- 结构化日志
- 日志严重级别
- 日志自定义处理
- 日志分组
初体验
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo1/main.go package main import ( "context" "log/slog" ) func main() { slog.Info("slog msg", "greeting", "hello slog") // 携带 context 上下文 slog.InfoContext(context.Background(), "slog msg with context", "greeting", "hello slog") }
在上述示例中,我们直接通过调用包函数 slog.Info
去输出一条 info
等级的日志。在该函数内部,会使用默认提供的一个 Logger
实例去执行日志输出的操作。除此之外,我们还能使用 slog.InfoContext
携带上下文进行日志输出。
除了 Info()
和 InfoContext()
函数,还有 Debug()
、Warn()
和 Error()
等输出不同级别日志的函数。
运行上面这段程序,会得到以下输出:
2023/10/08 21:08:08 INFO slog msg greeting="hello slog"
2023/10/08 21:08:08 INFO slog msg with context greeting="hello slog"
Logger 的创建
默认情况下,使用 slog
包函数输出日志,仅仅是普通的文本格式,若想实现 JSON
或者 key=value
的格式输出,需要使用 slog.New()
函数创建 Logger
实例,使用该函数时需要传入一个 slog.Handler
的实现,slog
包提供两个实现:TextHandler
和 JsonHandler
。
TextHandler 处理器
TextHandler
是一个日志记录处理器,它将记录以一系列键值对的形式写入到一个 io.Writer
中。每个键值对都以 key=value
的形式表示,并且它们之间用空格分隔。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo2/main.go package main import ( "context" "log/slog" "os" ) func main() { textLogger := slog.New(slog.NewTextHandler(os.Stdout, nil)) textLogger.InfoContext(context.Background(), "TextHandler", "姓名", "陈明勇") }
在上述示例中,我们通过 slog.NewTextHandler
函数创建一个日志处理器,第一个参数 os.Stdout
表示将日志输出到控制台,然后将处理器作为参数,传递到 slog.New
函数里创建一个 Logger
实例,通过该实例可以执行日志输出的相关操作。
程序运行结果如下所示:
time=2023-10-08T21:09:03.912+08:00 level=INFO msg=TextHandler 姓名=陈明勇
JsonHandler 处理器
JsonHandler
是一个日志处理器, 它将记录以 json
的形式写入到一个 io.Writer
中。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo3/main.go package main import ( "context" "log/slog" "os" ) func main() { jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) jsonLogger.InfoContext(context.Background(), "JsonHandler", "姓名", "陈明勇") }
在上述示例中,我们通过 slog.NewJsonHandler
函数创建一个 json
日志处理器,第一个参数 os.Stdout
表示将日志输出到控制台,然后将处理器作为参数传递到 slog.New
函数里创建一个 Logger
实例,通过该实例可以执行日志输出的相关操作。
程序运行结果如下所示:
{"time":"2023-10-08T21:09:22.614686104+08:00","level":"INFO","msg":"JsonHandler","姓名":"陈明勇"}
全局的 Logger 实例
slog
有一个默认的 Logger
实例,如果我们想要获取默认的 Logger
实例,可以参考以下代码:
logger := slog.Default()
在前面的示例中,我们一直使用创建的一个 Logger
实例去输出日志。然而,如果我们不想每次都需要通过特定的 Logger
实例来记录日志,而是希望能够全局操作,我们可以使用 slog.SetDefault
函数来设置并替换默认的 Logger
实例。这种方式可以使日志记录更加方便和灵活。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo4/main.go package main import ( "context" "log/slog" "os" ) func main() { jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) slog.SetDefault(jsonLogger) slog.InfoContext(context.Background(), "JsonHandler", "姓名", "陈明勇") //{"time":"2023-10-08T21:11:22.41760604+08:00","level":"INFO","msg":"JsonHandler","姓名":"陈明勇"} }
Group 分组
分组指的给日志记录相关联的属性(键值对)进行分组,通过示例感受一下:
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo5/main.go package main import ( "context" "log/slog" "os" ) func main() { jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil)).WithGroup("information") jsonLogger.InfoContext(context.Background(), "json-log", slog.String("name", "chenmingyong"), slog.Int("phone", 1234567890)) textLogger := slog.New(slog.NewTextHandler(os.Stdout, nil)).WithGroup("information") textLogger.InfoContext(context.Background(), "json-log", slog.String("name", "chenmingyong"), slog.Int("phone", 1234567890)) }
运行这段程序的结果如下所示:
{"time":"2023-10-08T21:12:23.124255258+08:00","level":"INFO","msg":"json-log","information":{"name":"chenmingyong","phone":1234567890}}
time=2023-10-08T21:12:23.127+08:00 level=INFO msg=json-log information.name=chenmingyong information.phone=1234567890
根据运行结果可知,如果是对具有 JsonHandler
处理器的 Logger
实例进行分组操作,输出日志时,组名 group name
将作为 key
,value
则是所有键值对组成的一个 json
对象。
如果是对具有 TextHandler
处理器的 Logger
实例进行分组操作,组名 group name
将与所有键值对的键进行组合,最终以 groupName.key=value
的形式展示。
LogAttrs 高效输出日志
如果需要频繁输出日志,相比之前的例子,我们使用 slog.LogAttrs
函数结合 slog.Attr
类型的去输出日志会更 高效,因为减少了类型解析的过程。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo6/main.go package main import ( "context" "log/slog" "os" ) func main() { jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) jsonLogger.LogAttrs(context.Background(), slog.LevelInfo, "高效输出日志", slog.String("姓名", "陈明勇"), slog.Int("联系方式", 12345678901)) }
在上面的示例中,我们使用了 LogAttrs
方法去输出一条日志,该方法的签名为:func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr)
。
结合方法签名我们可以知道,第一个参数为 context.Context
上下文类型,第二个参数为 Level
类型,即 slog
包里面的日志严重级别类型,第三个参数为 Attr
键值对类型。
在使用其他方法如 Info
输出日志时,内部会将键值对转成 Attr
类型,而使用 LogAttrs
方法,我们直接指定了 Attr
类型,减少了转换的过程,因此会更 高效。
With 设置统一的属性
如果每条日志都包含相同的一个键值对,我们可以考虑设置一个统一的属性。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo7/main.go package main import ( "context" "log/slog" "os" ) func main() { jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) logger := jsonLogger.With("systemID", "s1") logger.LogAttrs(context.Background(), slog.LevelInfo, "json-log", slog.String("k1", "v1")) logger.LogAttrs(context.Background(), slog.LevelInfo, "json-log", slog.String("k2", "v2")) }
我们可以使用 With
方法添加一个或多个固定属性,并返回一个新的 Logger
实例,后面通过新实例输出的日志都会包含 被添加的固定属性,从而 避免 每条输出的日志语句添加 相同 的键值对。
运行这段程序的结果如下所示:
{"time":"2023-10-08T21:19:51.338328238+08:00","level":"INFO","msg":"json-log","systemID":"s1","k1":"v1"}
{"time":"2023-10-08T21:19:51.338604943+08:00","level":"INFO","msg":"json-log","systemID":"s1","k2":"v2"}
HandlerOptions 日志处理器的配置选项
细心的读者也许能发现,在前面的示例中,无论是 NewJSONHandler
,还是 NewTextHandler
,第二个参数都被设置为 nil
,这是为了使用默认的配置。
这个参数的类型为 *HandlerOptions
,通过它,我们可以配置是否显示日志语句的源代码位置信息、最低的日志输出级别以及键值对属性的重写操作。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo8/main.go package main import ( "context" "log/slog" "os" "time" ) func main() { jsonLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ AddSource: true, Level: slog.LevelError, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey { if t, ok := a.Value.Any().(time.Time); ok { a.Value = slog.StringValue(t.Format(time.DateTime)) } } return a }, })) jsonLogger.InfoContext(context.Background(), "json-log", slog.String("姓名", "陈明勇")) jsonLogger.ErrorContext(context.Background(), "json-log", slog.String("姓名", "陈明勇")) }
在上述示例中,我们创建了一个具有 JsonHanlder
处理器的 Logger
实例。在创建 JsonHanlder
时,通过 HandlerOptions
参数指定了以下配置:
- 输出日志语句的源代码配置
Source
信息 - 设置最低日志等级为
Error
- 将
key
为"time"
的属性值的格式重写为"2006-01-02 15:04:05"
的形式。
运行这段程序得到的结果如下所示:
{"time":"2023-10-08 21:21:31","level":"ERROR","source":{"function":"main.main","file":"D:/goproject/src/gocode/play/main.go","line":24},"msg":"json-log"
,"姓名":"陈明勇"}
输出结果与预期相同,INFO
等级的日志没有被输出、输出 Source
信息以及重写 key
为 "time"
的属性值。
自定义 key-value 对中 value 的值
在前面的一个案例中,我们有通过 HandlerOptions
配置来修改 key-value
对中 value
的值,除此之外,slog
包还支持使用另一种方式进行修改。
// github.com/chenmingyong0423/blog/blob/master/tutorial-code/slog/demo9/main.go package main import ( "context" "log/slog" ) type Password string func (Password) LogValue() slog.Value { return slog.StringValue("REDACTED_PASSWORD") } func main() { slog.LogAttrs(context.Background(), slog.LevelInfo, "敏感数据", slog.Any("password", Password("1234567890"))) }
在上述案例中,我们通过实现 slog.LogValuer
接口(为某个类型添加 LogValue() slog.Value
方法),将 key-value
对中 value
的值进行重写。日志输出时,value
的值将会被 LogValue
方法的返回值所覆盖。
运行这段程序输出的结果如下所示:
2023/10/08 21:37:11 INFO 敏感数据 password=REDACTED_PASSWORD
输出结果与预期结果相同,password
的 value
已经被修改。
到此这篇关于Go语言结构化日志slog的用法解析的文章就介绍到这了,更多相关go slog内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
最新评论