记福uu链路追踪重构
埋点
所谓埋点(instrumentation),就是往你代码里加点逻辑(通常调调api就行),使得可观测平台(e.g. OpenTelemetry)能够从你的程序中导出需要监控的数据(学名:遥测数据),分为:链路(traces)、指标(metrics)、日志(logs),后续就是将数据呈现在看板上,实现可观测性。
分为手动埋点和调用埋点库埋点。
- 手动埋点:自己调 Otel sdk 埋点。优点:流程可控、灵活度高;缺点:自己写麻烦
- 埋点库:第三方框架往往提供自己的可观测性埋点代码,直接调。优点:标准统一,代码现成;缺点:没啥缺点
Otel 官方是推荐优先第三方埋点库的,毕竟生态好,市面上的框架都有。
福uu 需要埋点的地方有:
- api网关(http,使用了Hertz框架)
- 微服务(rpc,使用了Kitex框架)
- 基础设施/中间件
- db(使用 gorm)
- cache(使用 redis)
- cronjob / 异步 task(使用自己实现的本地队列 taskqueue)
- jwch/yjsy 等外部调用(http)
- …
现已实现大部分埋点:
还未完成:
- taskqueue 的异步task 部分
- 外部调用
- 待发现
Trace 打通 logger
福uu 服务端实现有自己的 logger (github.com/…/pkg/logger/) 。在没有引入监控体系前,调用 logger 的行为是:
1 | |
简单的日志打印,没有附加 trace 信息,无法根据日志锁定所属 trace / span 等链路信息。
现已打通 Trace 和 logger,使得在日志中能够附带记录当下这条日志所属 trace(链路)和 span(链路中的节点)。调用 logger 的行为变成:
1 | |
可以看到,客户代码改的很轻,监控信息通过 context.Context 即可透传进 logger,后者内部解析出 span 上下文,包装成若干额外的 zap.Field ,注入日志打印。在开发者侧,其实他们并没有,也不需要感知到链路追踪的实现细节,整个链路信息的记录和日志是程序在背后自动完成的,符合偏基础设施模块的设计准则。
成果展示:
下面是请求 /api/v1/trace/ping 这条链路的日志记录:
http
rpc

- 日志新增了
trace_id和span_id等链路语境下的参数 - 同一条链路下,
trace_id相同,不同节点的span_id不同
Error 日志时自动处理当前 span
我们的需求是:
如果链路中出现了错误,我们想记录这个错误,使之能在监控看板上反映出来,方便排查。
那么 logger 系统就是一个很好的切入点:将某个阈值 level 以上的日志,算作链路报错,自动将它所属的 span 标红,就能记录错误到监控后端。
大致代码:
1 | |
对应实现:071000f
题外话:这里设计接口时还考虑过是否需要显式调用,让开发者手动控制 span:
1 | |
因为两个方法的使用场景有细微区别,RecordError 仅仅是记录当前 span 上发生的错误;后面的 SetStatus 直接会把这个 span 挂掉。有时候只想记录错误但不想挂掉,如果不区分可能会影响业务的语义和监控的准确性。但显式调用也会提高开发者的学习成本,为了正确调用,他们就需要了解链路追踪这层的运行原理和实现。
最终权衡利弊下还是放弃了显式定义方法,因为设计的初衷还是要保证链路追踪这层基础设施的透明性,尽量不增加开发者的负担。
日志导出为 Otel logs
对应实现:a276411
Span.IsRecording()
这个接口判断当前的 Span 是否真的在记录数据。
实际 OpenTelemetry 中,有些 span 会正常被采样(sampled),也有些不会(dropped)。后者仍会保留为了 api 的兼容性考虑,但它本质上就是一个空壳的 span(non-recording span)。
关于这个接口函数,到底要不要在 span.AddEvent / span.RecordError / span.SetStatus 之前先做判断呢?答案是大部分情况不需要。其实在 Span 的内部实现中,做以上操作前已经帮你判断过一遍了。
那么这个函数为什么还要当作公共接口导出呢?其实是为了少部分情况下提升性能,避免客户代码做昂贵的操作,比如,SetAttributes() 可能构造昂贵的字符串、JSON等数据。
1 | |
Reference
- 福uu链路追踪重构方案:https://west2-online.feishu.cn/wiki/QCyKwL4YGicgJgk1GWUcz3gLnqe
- Uptrace 文档:https://uptrace.dev/get
- 使用 Uptrace-go 埋点:https://uptrace.dev/get/opentelemetry-go
- 直接 exporter 导出到 Uptrace:https://uptrace.dev/get/opentelemetry-go/otlp
- OpenTelemetry 相关的埋点库:
- OpenTelemetry 中常见的 attributes:OpenTelemetry semantic conventions
- OpenTelemetry Logger:https://opentelemetry.io/docs/languages/go/instrumentation/#logs