![]()
Go 1.21发布时,标准库里多了个叫log/slog的包。没发布会,没热搜,但用上的团队发现一件事:以前查故障像翻垃圾堆,现在像调数据库。
这中间的差距,是结构化日志。不是新词,但Go官方这次把它做成了默认选项——意味着从今往后,"打印字符串"和"输出事件"在Go里是两条路了。
从"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的设计像乐高,三块核心组件拼出所有场景。
记录(Record)是一次发生的事情。包含时间、级别、消息、一组属性。你用Info、Error这些方法创建,或者用Log方法显式指定级别。级别不是装饰,是过滤的第一道闸门。
属性(Attributes)是key-value对,让日志可查询的根基。slog文档警告过一个细节:同一概念用三个不同key(user、userId、uid),就会得到三个不相干的数据集。一致性比格式更重要。
处理器(Handler)决定记录怎么变成字节。内置两个:TextHandler输出key=value格式,JSONHandler输出行分隔JSON。复杂逻辑也在这里——脱敏、改名、路由到不同后端。
一个被低估的设计:slog能罩住旧代码。设一个默认logger后,顶层函数自动用它,连经典的log包都能重定向过来。迁移不用大爆炸,可以一块一块换。
Group:解决"每个子系统都用id"的命名灾难
![]()
微服务里常见场景:HTTP层有request ID,数据库层也有request ID,缓存层还有。三个id,三个意思,日志里撞车。
slog的Group机制给key加命名空间。你可以把一组属性归到request.method、request.path下面,或者用WithGroup把整个子系统包起来。key不再打架,查询时能精确定位到层级。
这功能不炫,但省下的时间很实在。某电商团队在迁移后反馈,定位跨服务问题的平均时间从40分钟降到8分钟——不是因为工具变快了,是因为不用先开会统一字段叫法。
生产环境的隐形规则
slog文档里埋了一个建议:把日志想成进程发出的事件流,路由和存储放在应用外面。这个心智模型推着你往一个方向写——每行一个事件,事件要容易传输、容易重处理。
具体怎么做?别在日志里做格式化决策,交给Handler。别攒多条日志再刷,行缓冲足够。别假设日志会被本地grep,假设它会被扔进Loki或CloudWatch。
Go 1.21的发布时间线是2023年8月。到现在一年半,slog已经从"新玩具"变成很多团队的默认配置。标准库的位置意味着它不会突然废弃,也意味着第三方库的适配成本在下降。
一个细节:slog的JSONHandler默认用RFC3339毫秒精度时间戳,和Prometheus、Grafana的解析习惯对齐。不是巧合,是Go团队在可观测性生态里的有意卡点。
迁移slog的团队里,最常见后悔是什么?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.