Go 结构化日志实战:用标准库 slog 输出可检索、可分级的日志

test12026-09-303 次阅读

为什么放弃 fmt.Println

开发期用 fmt.Println 调试很顺手,但到了生产环境,散落各处的字符串日志既不便检索,也难区分级别。Go 1.21 起标准库自带 slog,用键值对输出结构化日志,配合 JSON handler 能直接被 ELK、Loki 这类系统采集和过滤。

用 slog 标准库输出结构化日志

最简方式是用 slog.SetDefault 设定一个 JSON handler,之后全局调用 slog.Info / slog.Warn 都会带上统一格式和附加字段。

package main

import (
	"context"
	"log/slog"
	"os"
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))
	slog.SetDefault(logger)

	slog.Info("服务启动",
		slog.String("module", "gateway"),
		slog.Int("port", 8080),
	)

	ctx := context.Background()
	// 把请求级字段塞进 logger,下游直接取用,不必层层传参
	reqLogger := slog.With(slog.String("trace_id", "abc123"))
	reqLogger.WarnContext(ctx, "慢查询", slog.Float64("cost_ms", 320.5))
}

在 HTTP 中间件里串联请求日志

把每次请求的方法、路径、耗时作为一条结构化日志记录,是排查线上问题的第一手资料。

func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		next.ServeHTTP(w, r)
		slog.Info("http_request",
			slog.String("method", r.Method),
			slog.String("path", r.URL.Path),
			slog.Duration("cost", time.Since(start)),
		)
	})
}

级别与字段使用要点

  • 用 slog.String / slog.Int / slog.Duration 等类型化方法加字段,比拼接字符串更利于日志系统按字段检索。
  • 不要在日志里打印密钥、 token、身份证号等敏感信息,结构化之后这些字段更容易被明文采集。
  • 高并发场景 JSON handler 比 text handler 更利于机器解析,也方便统一接入采集管道。
  • 把 trace_id、user_id 这类请求级字段通过 slog.With 绑定到子 logger,全链路日志都能带上,方便串联。
T

test1

文章作者

为什么放弃 fmt.Println 开发期用 fmt.Println 调试很顺手,但到了生产环境,散落各处的字符串日志...

分类
技术
发布时间
2026-09-30
字数
约 1364 字
阅读
3 次