map 的泛型需要两种类型,一个 key 类型和一个 value 类型。值类型没有任何限制,但键类型应该始终满足 comparable 约束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// keys 返回一个 map 的所有 key // m 参数是使用了 K 和 V 泛型的 map // K 是使用了 comparable 约束的泛型,也就是说 K 必须支持 != 和 == 操作 // V 是使用了 any 约束的泛型,也就是说 V 可以是任意类型 funckeys[Kcomparable, Vany](m map[K]V) []K { // 创建一个长度为 map 长度的 K 类型的 slice key := make([]K, len(m)) i := 0 for k, _ := range m { key[i] = k i++ } return key }
结构体中的泛型
Go 允许使用类型参数定义 struct 。语法类似于泛型函数。类型参数可用于结构体上的方法和数据成员。
1 2 3 4 5 6 7 8 9 10 11 12
// T 是类型参数,使用了 any 约束 type MyStruct[T any] struct { inner T }
拥有如此多独立的代码检查工具的问题在于你必须自己下载每个单独的代码检查工具并管理它们的版本。 此外,依次运行每一个可能会太慢。因此,golangci-lint,一个Go代码检查工具聚合器,可以并行运行代码检查工具,重用 Go 构建缓存,并缓存分析结果,从而在后续运行中大大提高性能,是在 Go 项目中设置代码检查的首选方式。
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
安装完成后,您应该检查已安装的版本:
1 2
➜ ~ golangci-lint version golangci-lint has version v1.55.2 built with go1.21.6 from (unknown, mod sum: "h1:yllEIsSJ7MtlDBwDJ9IMBkyEUz2fYE0b5B8IUgO1oP8=") on (unknown)
您还可以通过以下命令查看所有可用的代码检查器:
1
golangci-lint help linters
输出:
1 2 3 4 5
Enabled by default linters: errcheck: errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] gosimple (megacheck): Linter for Go source code that specializes in simplifying code [fast: false, auto-fix: false] govet (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false] ......
// 添加上下文属性到 Record 中,然后调用底层的 handler func(h ContextHandler) Handle(ctx context.Context, r slog.Record) error { if attrs, ok := ctx.Value(slogFields).([]slog.Attr); ok { for _, v := range attrs { r.AddAttrs(v) } }
return h.Handler.Handle(ctx, r) }
// AppendCtx 将 slog 属性添加到提供的上下文中, // 以便在使用此类上下文创建的任何 Record 中都会包含该属性 funcAppendCtx(parent context.Context, attr slog.Attr) context.Context { if parent == nil { parent = context.Background() }
if v, ok := parent.Value(slogFields).([]slog.Attr); ok { v = append(v, attr) return context.WithValue(parent, slogFields, v) }
v := []slog.Attr{} v = append(v, attr) return context.WithValue(parent, slogFields, v) }
该 ContextHandler 结构嵌入了 slog.Handler 接口,并实现了 Handle 方法,以提取存储在提供的上下文中的 Slog 属性。如果找到,它们将被添加到 Record 中,然后调用底层的 Handler 来格式化和输出记录。
for i, v := range frames { f := stackFrame{ Source: filepath.Join( filepath.Base(filepath.Dir(v.File)), filepath.Base(v.File), ), Func: filepath.Base(v.Function), Line: v.Line, }
// User 没有实现 `LogValuer` 接口 type User struct { ID string`json:"id"` FirstName string`json:"first_name"` LastName string`json:"last_name"` Email string`json:"email"` Password string`json:"password"` }
for i := 1; i <= 10; i++ { logger.Info(fmt.Sprintf("a message from the gods: %d", i)) } }
输出:
1 2
{"time":"2023-10-18T19:14:09.820090798+02:00","level":"INFO","msg":"a message from the gods: 4"} {"time":"2023-10-18T19:14:09.820117844+02:00","level":"INFO","msg":"a message from the gods: 5"}
sugar.Info("Hello from Zap logger!") sugar.Infoln( "Hello from Zap logger!", ) sugar.Infof( "Hello from Zap logger! The time is %s", time.Now().Format("03:04 AM"), )
{"level":"info","ts":1684147807.960761,"caller":"zap/main.go:17","msg":"Hello from Zap logger!"} {"level":"info","ts":1684147807.960845,"caller":"zap/main.go:18","msg":"Hello from Zap logger!"} {"level":"info","ts":1684147807.960909,"caller":"zap/main.go:21","msg":"Hello from Zap logger! The time is 11:50 AM"} {"level":"info","ts":1684148355.2692218,"caller":"zap/main.go:25","msg":"User logged in","username":"johndoe","userid":123456,"provider":"google"}
logger.Warn("User account is nearing the storage limit", zap.String("username", "john.doe"), zap.Float64("storageUsed", 4.5), zap.Float64("storageLimit", 5.0), )
1
{"level":"warn","ts":1684166023.952419,"caller":"zap/main.go:46","msg":"User account is nearing the storage limit","username":"john.doe","storageUsed":4.5,"storageLimit":5}
childLogger.Info("redirecting user to admin dashboard") }
注意两个日志中都存在 service 和 requestID
1 2
{"level":"info","ts":1684164941.7644951,"caller":"zap/main.go:52","msg":"user registration successful","service":"userService","requestID":"abc123","username":"john.doe","email":"john@example.com"} {"level":"info","ts":1684164941.764551,"caller":"zap/main.go:57","msg":"redirecting user to admin dashboard","service":"userService","requestID":"abc123"}
您可以使用相同的方法向所有日志添加全局元数据。例如,您可以像这样做,将程序的进程ID和编译程序所使用的 Go 版本包含在所有记录中:
logger.Error("Failed to perform an operation", zap.String("operation", "someOperation"), zap.Error(errors.New("something happened")), // the key will be `error` here zap.Int("retryAttempts", 3), zap.String("user", "john.doe"), )
输出:
1
{"level":"error","ts":1684164638.0570025,"caller":"zap/main.go:47","msg":"Failed to perform an operation","operation":"someOperation","error":"something happened","retryAttempts":3,"user":"john.doe","stacktrace":"main.main\n\t/home/ayo/dev/demo/zap/main.go:47\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:250"}
{"level":"fatal","ts":1684170760.2103574,"caller":"zap/main.go:47","msg":"Something went terribly wrong","context":"main","code":500,"error":"An error occurred","stacktrace":"main.main\n\t/home/ayo/dev/demo/zap/main.go:47\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:250"} exit status 1
for i := 1; i <= 10; i++ { logger.Info("an info message") logger.Warn("a warning") } }
因此,您应该只看到六个日志条目,而不是观察 20 个。
1 2 3 4 5 6
{"level":"info","timestamp":"2023-05-17T16:00:17.611+0100","msg":"an info message"} {"level":"warn","timestamp":"2023-05-17T16:00:17.611+0100","msg":"a warning"} {"level":"info","timestamp":"2023-05-17T16:00:17.611+0100","msg":"an info message"} {"level":"warn","timestamp":"2023-05-17T16:00:17.611+0100","msg":"a warning"} {"level":"info","timestamp":"2023-05-17T16:00:17.611+0100","msg":"an info message"} {"level":"warn","timestamp":"2023-05-17T16:00:17.611+0100","msg":"a warning"}
type SensitiveFieldEncoder struct { zapcore.Encoder cfg zapcore.EncoderConfig }
// EncodeEntry is called for every log line to be emitted so it needs to be // as efficient as possible so that you don't negate the speed/memory advantages // of Zap func(e *SensitiveFieldEncoder) EncodeEntry( entry zapcore.Entry, fields []zapcore.Field, ) (*buffer.Buffer, error) { filtered := make([]zapcore.Field, 0, len(fields))
for _, field := range fields { user, ok := field.Interface.(User) if ok { user.Email = "[REDACTED]" field.Interface = user }
{"level":"info","timestamp":"2023-05-20T09:14:46.043+0100","msg":"an info log","name":"main","user":{"id":"USR-12345","name":"John Doe","email":"john.doe@example.com"}}
{"level":"info","timestamp":"2023-05-20T09:28:31.231+0100","msg":"an info log","name":"main","user":{"id":"USR-12345","name":"John Doe","email":"[REDACTED]"}}
然而,请注意,自定义编码器不会影响使用 With() 方法附加的字段,因此如果您这样做:
1 2
child := logger.With(zap.String("name", "main"), zap.Any("user", u)) child.Info("an info log")
{"level":"info","timestamp":"2023-05-20T09:31:11.919+0100","msg":"an info log","name":"main","user":{"id":"USR-12345","name":"John Doe","email":"john.doe@example.com"}}
将 Zap 用作 Slog 的后端
Go 语言引入了新的结构化日志包Slog后,开始着手在 Zap 中实现 slog.Handler 接口,以便利用 Slog API 与 Zap 后端。这种集成确保了在各种依赖项中日志 API 的一致性,并便于无需大幅更改代码即可无缝切换日志包。
目前为止,Slog 尚未包含在官方的 Go 发布版本中。因此,Zap 与 Slog 的官方集成已经提供在一个单独的模块中,可以使用以下命令进行安装: