打开网易新闻 查看精彩图片

Go 1.21发布时,标准库里多了个叫log/slog的包。没发布会,没热搜,但用上的团队发现一件事:以前查故障像翻垃圾堆,现在像调数据库。

这中间的差距,是结构化日志。不是新词,但Go官方这次把它做成了默认选项——意味着从今往后,"打印字符串"和"输出事件"在Go里是两条路了。

从"grep大赛"到"直接查字段"

从"grep大赛"到"直接查字段"

传统日志长这样:failed to charge card user=42 amount=19.99 ms=842 err=timeout。人眼能读,机器头疼。你想过滤所有超时?先写正则,再祈祷格式没变异体。

slog的JSON输出长这样:{"msg":"failed to charge card","user_id":42,"amount":19.99,"duration_ms":842,"error":"timeout"}。每个字段有名字、有类型、有位置。日志系统不用猜,直接按key聚合。

日志的本质变了:从"给人看的句子"变成"给机器读的事件流"。

Go官方文档里有句话很准:「Logs are a debugging interface you can still use when the system is on fire.」系统着火时,你唯一还能用的调试接口。但前提是,这个接口不能需要你现场写正则表达式。

打开网易新闻 查看精彩图片

slog的三块积木:记录、属性、处理器

slog的三块积木:记录、属性、处理器

slog的设计像乐高,三块核心组件拼出所有场景。

记录(Record)是一次发生的事情。包含时间、级别、消息、一组属性。你用InfoError这些方法创建,或者用Log方法显式指定级别。级别不是装饰,是过滤的第一道闸门。

属性(Attributes)是key-value对,让日志可查询的根基。slog文档警告过一个细节:同一概念用三个不同key(user、userId、uid),就会得到三个不相干的数据集。一致性比格式更重要。

处理器(Handler)决定记录怎么变成字节。内置两个:TextHandler输出key=value格式,JSONHandler输出行分隔JSON。复杂逻辑也在这里——脱敏、改名、路由到不同后端。

一个被低估的设计:slog能罩住旧代码。设一个默认logger后,顶层函数自动用它,连经典的log包都能重定向过来。迁移不用大爆炸,可以一块一块换。

Group:解决"每个子系统都用id"的命名灾难

Group:解决"每个子系统都用id"的命名灾难

打开网易新闻 查看精彩图片

微服务里常见场景:HTTP层有request ID,数据库层也有request ID,缓存层还有。三个id,三个意思,日志里撞车。

slog的Group机制给key加命名空间。你可以把一组属性归到request.methodrequest.path下面,或者用WithGroup把整个子系统包起来。key不再打架,查询时能精确定位到层级。

这功能不炫,但省下的时间很实在。某电商团队在迁移后反馈,定位跨服务问题的平均时间从40分钟降到8分钟——不是因为工具变快了,是因为不用先开会统一字段叫法。

生产环境的隐形规则

生产环境的隐形规则

slog文档里埋了一个建议:把日志想成进程发出的事件流,路由和存储放在应用外面。这个心智模型推着你往一个方向写——每行一个事件,事件要容易传输、容易重处理。

具体怎么做?别在日志里做格式化决策,交给Handler。别攒多条日志再刷,行缓冲足够。别假设日志会被本地grep,假设它会被扔进Loki或CloudWatch。

Go 1.21的发布时间线是2023年8月。到现在一年半,slog已经从"新玩具"变成很多团队的默认配置。标准库的位置意味着它不会突然废弃,也意味着第三方库的适配成本在下降。

一个细节:slog的JSONHandler默认用RFC3339毫秒精度时间戳,和Prometheus、Grafana的解析习惯对齐。不是巧合,是Go团队在可观测性生态里的有意卡点。

迁移slog的团队里,最常见后悔是什么?