Go Slog 实战指南:标准库结构化日志从入门到生产配置

Go log/slog 中文实战指南:默认 logger 与四个级别、TextHandler/JSONHandler、结构化属性与分组、With 链式上下文、HandlerOptions 定制(AddSource/ReplaceAttr/LevelVar 动态级别),以及如何用观测云 DataKit 采集 Go 日志并配置告警。

最佳实践
Go Slog 实战指南:标准库结构化日志从入门到生产配置技术指南封面

Slog 是 Go 1.21 起内置在标准库中的结构化日志包(log/slog),它把"高性能 + 结构化 + 前后端分离(Handler 可替换)"这三件事第一次同时带进了标准库。本文从默认用法讲到生产配置,帮助你把 Go 项目的日志体系一次性搭对。

核心要点速览

  • Slog 是 Go 标准库的结构化日志方案log/slog 提供 Debug/Info/Warn/Error 四个级别,默认输出带时间戳和级别的文本。
  • 两种内置 HandlerTextHandler 输出 logfmt 风格键值对适合开发,JSONHandler 输出 JSON 适合生产采集。
  • 结构化靠属性(Attr)slog.String("key", v)logger.With() 绑定常驻字段、slog.Group() 分组嵌套。
  • 生产落地:JSONHandler 写 stdout,观测云 DataKit 自动采集解析,监控器对 error 级日志告警。

快速上手:默认 logger 与日志级别

Slog 提供一个开箱即用的默认 logger,直接调包级函数即可:

package main

import "log/slog"

func main() {
    slog.Debug("调试细节")          // 默认不输出(默认级别 INFO)
    slog.Info("服务启动", "port", 8080)
    slog.Warn("配置项缺失,使用默认值")
    slog.Error("数据库连接失败", "err", "connection refused")
}

输出:

2026/08/25 14:30:22 INFO 服务启动 port=8080
2026/08/25 14:30:22 WARN 配置项缺失,使用默认值
2026/08/25 14:30:22 ERROR 数据库连接失败 err="connection refused"

四个级别对应数值:Debug=-4、Info=0、Warn=4、Error=8。数值设计留下了自定义中间级别的空间(如 2 表示 Info+2 的 Notice)。

如何用 Handler 控制输出格式与目的地?

Slog 的设计精髓是前端 API 与后端 Handler 分离:业务代码只调 slog API,输出格式和目的地由 Handler 决定。

TextHandler:开发环境的人类可读格式

logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
logger.Info("用户登录", "user", "zhangsan", "ip", "10.0.0.8")

输出 logfmt 风格键值对:

time=2026-08-25T14:31:05.123+08:00 level=INFO msg=用户登录 user=zhangsan ip=10.0.0.8

JSONHandler:生产环境的结构化格式

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("用户登录", "user", "zhangsan", "ip", "10.0.0.8")
{"time":"2026-08-25T14:31:05.123456+08:00","level":"INFO","msg":"用户登录","user":"zhangsan","ip":"10.0.0.8"}

JSON 输出是接入日志平台的标准姿势——观测云 DataKit 采集后自动解析为字段,无需任何正则。

结构化三板斧:属性、With 与分组

强类型属性

消息后面跟键值对,推荐使用强类型的 Attr 构造器(性能更好、类型安全):

logger.Info("订单创建成功",
    slog.String("order_id", "A-1024"),
    slog.Int("amount", 9900),
    slog.Duration("elapsed", 35*time.Millisecond),
)

注意避免 logger.Info("msg", "key", value) 松散写法的键值不配对错乱——编译器不检查,配错就静默产生 !BADKEY 字段。用 slog.String() 等强类型形式可根治。

With:给 logger 绑定常驻字段

同一请求/模块的多条日志共享的字段,用 With 绑定一次:

reqLogger := logger.With(slog.String("request_id", rid), slog.String("user", "zhangsan"))
reqLogger.Info("开始处理订单")
reqLogger.Info("订单处理完成")  // 两条日志都自动带 request_id 和 user

Group:字段分组嵌套

logger.Info("支付回调",
    slog.Group("payload",
        slog.String("order_id", "A-1024"),
        slog.Int("amount", 9900),
    ),
)

JSON 输出中 payload 会成为嵌套对象,字段结构更清晰。

生产配置:HandlerOptions 详解

opts := &slog.HandlerOptions{
    Level:     slog.LevelInfo,   // 最低输出级别
    AddSource: true,             // 附加源码位置(文件:行号)
    ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
        // 例如把 level 字段值转小写,统一风格
        if a.Key == slog.LevelKey {
            a.Value = slog.StringValue(strings.ToLower(a.Value.String()))
        }
        return a
    },
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))
  • Level 接受 slog.LevelVar,运行期可动态调整——配合 HTTP 管理接口即可实现不重启调级别;
  • AddSource 排障期很有用,但有性能开销,生产可关;
  • ReplaceAttr 是统一字段命名规范(如 time→@timestamp、level→status)的官方扩展点。

最后用 slog.SetDefault(logger) 让包级函数和 log 标准库的存量调用(通过 slog.NewLogLogger 桥接)都走新配置。

错误日志怎么记?

Go 的错误是值,直接作为属性记录:

if err := processPayment(order); err != nil {
    logger.Error("支付处理失败",
        slog.String("order_id", order.ID),
        slog.Any("error", err),
    )
}

想让自定义错误类型输出更丰富的结构,可以实现 slog.LogValuer 接口,把错误码、原因链等作为分组字段输出。Slog 不自动带堆栈——需要堆栈时用 runtime/debug.Stack() 或借助包装库(如 pkg/errors)附加上下文。

观测云落地:Go 服务日志采集与分析

  1. 应用侧:生产环境固定 JSONHandler + stdout 输出(容器最佳实践);字段命名遵循统一契约(time/level/msg/error)。
  2. DataKit 采集:容器环境自动采集 stdout;主机部署在 conf.d/log/logging.conf 配置文件采集,source 设为 go-appservice 设为服务名。JSON 自动解析为字段。
  3. Pipeline 标准化:把 level 映射为观测云标准 status 字段(error → error,warn → warning),time 解析为事件时间,保证查看器和告警按统一维度工作。
  4. 检索分析:日志查看器按 servicestatus、request_id 过滤;聚类分析自动归纳错误模式;图表模式统计错误率趋势。
  5. 告警:监控 > 监控器 > 新建日志检测监控器,对 status:error 日志设阈值,告警策略绑定钉钉/企业微信/飞书通知对象。
  6. 链路关联:接入 OpenTelemetry 后,用 slog 的 context 方法(见下一篇)把 trace_id 注入日志,实现与观测云 APM 的双向跳转。
  7. 成本控制:按级别拆索引——error 走标准索引长保留,info 走低频索引短保留,历史数据转发归档对象存储。

常见问题(FAQ)

Go 1.21 之前的版本能用 Slog 吗?

可以,使用 golang.org/x/exp/slog 实验包,API 与标准库一致。升级到 Go 1.21+ 后把 import 换成 log/slog 即可。新项目直接用标准库版本。

Slog 的性能比得上 Zerolog/Zap 吗?

Slog 明显快于 Logrus 等老库,但与 Zerolog(零分配)和 Zap 仍有差距。绝大多数业务系统感知不到差异;只有确认日志是性能瓶颈的极高吞吐服务,才需要考虑 Zerolog/Zap——而且可以用它们作为 Slog 的 Handler 后端,业务代码不变。

Slog 支持日志文件轮转吗?

标准库不含轮转。两种推荐做法:容器化部署写 stdout,轮转交给容器运行时;主机部署配合系统 logrotate(copytruncate 模式),或使用 lumberjack 等第三方 writer 作为 io.Writer 传给 Handler。

已有大量 log.Printf 的老项目怎么迁移?

分步走:先 slog.SetDefault + slog.NewLogLogger(handler, slog.LevelInfo) 把标准库 log 输出桥接到 slog Handler,存量代码不用动;新代码逐步用 slog API,老代码按模块渐进替换。

系列阅读


获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台