Go 日志字段规范:排障时别只剩一堆字符串

Go 日志字段规范:排障时别只剩一堆字符串
Go 日志字段规范排障时别只剩一堆字符串一、日志不是写给机器看的废话生产事故排查时日志经常决定定位速度。糟糕的日志是几万行字符串failed、error、timeout没有请求 ID、没有用户范围、没有下游、没有耗时。看起来打了很多日志真正排障时却没有证据。Go 服务日志要结构化字段要稳定语义要一致。我见过最糟糕的一次排障经历凌晨两点报警响了错误率飙升到 8%。打开日志平台看到上千条 request failed 和 downstream error。哪个接口报的错哪个下游超时了影响的是哪些用户日志里一个字都没有。四个人刷了四十分钟日志最后靠临时加日志重新部署才找到原因——某个下游的读库因为一条慢查询锁了表而日志之前只打印了 database error没有下游名、没有 SQL 指纹、没有耗时。事后一总结损失的不是代码能力是日志纪律。二、先定义公共字段flowchart TD A[请求进入] -- B[生成 request_id] B -- C[贯穿业务日志] C -- D[下游调用日志] D -- E[错误与耗时聚合]公共字段至少包括 request_id、route、method、tenant、user_scope、latency_ms、error_code、downstream。字段统一后日志平台才能聚合。log_fields: required: - request_id - route - latency_ms - error_code - downstream不要每个团队自己发明字段名。今天叫 requestId明天叫 trace_id查询会很痛苦。字段名最好由基础架构团队统一定义发布成团队规范文档。强推比自由发挥更省时间——查日志时不需要先查每种服务的字段名映射表。公共字段里还要考虑 user_hash 而非 user_id。不是所有日志系统都有完善的脱敏能力user_id 直接入库可能导致审计合规问题。hash 后仍然可以关联用户行为同时避免敏感数据暴露。三、Go 里统一封装 loggerfunc WithRequest(ctx context.Context, log *slog.Logger) *slog.Logger { rid, _ : ctx.Value(request_id).(string) return log.With(request_id, rid) }业务代码不应该每次手写 request_id。封装好上下文 logger让字段自动带上才能减少漏打。一个更容易被忽视的问题是 logger 的传递。HTTP handler 里拿到了带 request_id 的 ctx但在下游 goroutine 里如果不显式传递logger 就会丢失上下文。建议在 handler 层统一截获 ctx所有内部调用都走同一个携带 ctx 的方法不要在各个地方重新初始化 slog。Go 1.21 的 slog 已经原生支持 context用slog.DebugContext(ctx, ...)会自动关联 context 中的属性和 key。错误日志也要分级。用户参数错误、下游超时、系统 bug、权限拒绝不应该都打成 error。日志级别乱了告警也会乱。简单规则用户输入问题打 warn系统内部预期内的外部错误打 error非预期系统错误打 fatal或 error高优先级告警。日志级别不是存在感它是信号指示灯。四、不要记录敏感内容AI 应用尤其容易把 prompt、工具结果、文档内容打进日志。排障很方便但风险很大。日志里应保留 hash、长度、类型和引用 ID而不是完整敏感文本。log_redaction: drop_fields: - raw_prompt - access_token hash_fields: - document_id keep_fields: - prompt_tokens - model_name脱敏要在写日志前完成。不要指望日志平台后处理因为原文可能已经进入本地文件、缓冲区或第三方采集器。见过有团队在 Kafka 环节做脱敏结果发现本地文件轮转日志里明文 prompt 已经存了两周根本不需要经过 Kafka。脱敏必须发生在最接近写入的点——通常是应用层 logger 的 hook 或 middleware。还要给错误码建立字典。MODEL_TIMEOUT、RAG_NO_EVIDENCE、TOOL_PERMISSION_DENIED这种稳定错误码比自然语言错误更适合告警和统计。错误码字典还要有层级。第一级是领域AI、DB、CACHE第二级是类型TIMEOUT、REJECTED、UNAVAILABLE第三级是细节。这样监控大盘可以按不同粒度聚合告警规则也可以按错误码前缀匹配不需要一个一个配置。最后日志要和 Trace 对齐。日志里有 trace_idTrace 里有关键事件排障时才能从指标跳到调用链再跳到日志细节。日志字段还要控制基数。把 user_id、request_id 作为字段可以但不要把任意 prompt、动态 SQL、完整 URL 都变成标签。高基数字段会拖慢日志和指标系统也会让存储成本失控。log_cardinality_policy: label_fields: - route - error_code - downstream body_fields: - request_id - user_hash标签用于聚合正文用于排查。两者混在一起观测系统会很快变贵又难用。一个实用技巧日志平台搜索时标签查询比正文全文搜索快数倍。把高频查询维度放进标签低频字段放进正文既省钱又查得快。还要定期扫描日志样本确认敏感字段没有漏出。日志规范不是写完就结束代码迭代后很容易新增未脱敏字段。五、总结Go 日志字段规范要统一公共字段、封装上下文 logger、控制日志级别、脱敏敏感内容并建立稳定错误码。结构化日志的价值不在于好看而在于排障时能快速过滤、聚合、关联而不是在几十万行字符串里肉眼找线索。排障时需要的是可查询证据不是一堆看似热闹的字符串。