微服务 log 最佳实践

微服务设计中的 log 最佳实践。

可以利用现成的开源 log 库,例如 Uber 的go.uber.org/zap,性能高、接口友好、稳定性好。

在请求入口、出口处添加 log,以便发生错误后查找问题,也便于跨服务之间的责任划分。

请求入口处生成一个 traceId(可以利用github.com/google/uuid生成),用于该请求对应的 log 输出。通常这个 traceId 会保存在 Context 对象中,在调用过程中传递到不同函数,需要时取出 traceId。通常放在每行日志的最前面面、时间之后,这样不同请求(对应的不同携程)可以区分开来。

记录日志时,注意频率、容量,比如给 1000 个用户发送同一条消息,只需要打印消息内容+每个用户的 userId 即可,不需要把重复的消息打出来,可能造成性能问题。

记录日志时,确保服务器主机的日志输出是落到本地磁盘,而不是网络模拟磁盘。如果是网络模拟磁盘,可能造成输出日志抢占业务带宽的情况。

  • 不同日志等级:
    • info 对 log 划分等级,info 级别只输出最关键的信息,够后面追查就可以,默认只开启 info 级别日志。请求入口、出口处的日志级别为 info。其他级别还有 error、warn、debug。注意 fatal、panic 级别的 log 不能乱用,会导致程序退出,通常只在程序初始化时做及时退出的使用。error 日志输出时,通常伴有 error 对象值的输出。这时 error 对象中不应重复包含 tranceId,并且最好包含调用栈信息。(具体参照《Go error 最佳实践》)

输出 log 时,最好以结构化、Key-Value 形式输出 log,这样便于利用 ELK 收集、查询日志。

多个服务上下游之间,可以增加一个请求标记,利用 Jeager 进行链路追踪。

  • 设定日志配置:
    • 每个日志文件大小(通常是 1G),以便工具搜索查看
    • 日志名称,通常由日志库自动命名,最好能够按照名称排序与时间顺序一致,这样方便写脚本按时间分析日志。
    • 日志保留总容量/时间,保留时间不要太长,把磁盘占满;也不能太短,有可能需要分析 bug、恢复数据;