我不确定我的做法是不是 idiomatic 只是简单描述一下我的思路。毕竟 slog 进标准库也没多久,看 api 也是受 zap 影响很大。
大的逻辑方面我比较倾向于全局只有一个唯一 logger ,中间调用过程只传递。另外我习惯把业务层面的日志和功能实现层面的日志分开,然后两者用不同的方式来处理。
业务层面的日志我倾向于用格式化的字符串:一方面它是给运维用的,需要一眼看明白在哪个环节出了问题,但并不需要知道出问题的细节;另一方面这类日志要进日志服务器做后续统计等功能,需要方便处理。
对应的就是你的描述里 controller->service->repo 这个部分,我会用 errors 传递字符串,每个环节用 errorf 来 wrap 上一个环节的错误,起到简单的 stack trace 的功能。这里一般不建议用 runtime 来获取完整的 stack trace ,主要是性能开销的问题。
包或者实现层面的日志,一般是给开发者方便调试用的,比如看到底是什么入参触发了什么样的边界条件导致出错。这个信息在开发和测试环节比较有用,实际线上是相对冗余的信息。
日志记录这个信息我用的是自定义的 context ,因为标准库里的 context 实际上只有下文没有上文。可以看这个帖子
https://s.v2ex.com/t/1012453 我的回复。
用 context 的话可以自定义数据结构,能够比较好处理那些复杂的结构体,也就是你说的第二条里的麻烦。
对于 context 信息的记录就没有必要走业务层面的 logger 了。对于不太敏感又注重性能的业务,我的处理是加一个开关,当线上出现的问题无法复现的时候,开启开关再记录完整的 runtime stack trace 信息。对于相对敏感的业务,会单独用一个 logger 导出到日志服务器单独的表中,使用 traceId 对业务层面的日志和实现层面的日志做个关联。业务层面的日志要保留很久,但功能层面的日志一般三个月就可以清理了。