自定义组件 · 怎么扩展 Collector
几百个社区组件都是同一个模子刻出来的。这一章讲 Factory + Component 生命周期这套扩展契约、写一个 processor 要注意什么(尤其是 MutatesData 这个坑),以及用 OCB 组装自己的发行版。
自定义组件 · 怎么扩展 Collector
Collector 有几百个社区组件,而它们全是同一个模子刻出来的。这一章讲那个模子——顺便,读懂它你也就读懂了那几百个组件(它们都是同一模式的复制)。
这也是第 6 部分的收尾。
Component 接口:两个方法
// component/component.go:25(真实注释,节选)
type Component interface {
// Start tells the component to start. Host parameter can be used for communicating
// with the host after Start() has already returned. If an error is returned by
// Start() then the collector startup will be aborted.
//
// If the component needs to perform a long-running starting operation, then
// it is recommended that Start() returns quickly and the long-running
// operation is performed in the background. Background operations should
// create their own context using context.WithCancel(context.Background())
// rather than using the passed context, which is intended only for the
// startup operation itself.
Start(ctx context.Context, host Host) error
Shutdown(ctx context.Context) error
}注释里藏着两条很重要的工程纪律**:**
- “
Start()应该快速返回,长任务放后台” —— 因为Graph.StartAll要按顺序启动所有组件。一个组件在Start()里干半天(比如死等后端连上),会卡住整个 Collector 的启动。 - “后台任务要自己建 context,别用传进来的那个” —— 因为传进来的
ctx只代表“启动这个操作”的生命周期,Start()返回后它可能就被取消了。如果你的后台 goroutine 用了它,会莫名其妙提前退出。
这是个真实的、常见的 Go 陷阱——把“启动用的 context”当成“组件生命周期的 context”。正确做法是 context.WithCancel(context.Background()),然后在 Shutdown() 里 cancel。 注释把这条写得如此详细,说明踩的人不少。
Factory:注册的入口
官方文档点名的模式:每类组件都有 Factory + NewFactory。写一个 processor 大致是:
func NewFactory() processor.Factory {
return processor.NewFactory(
component.MustNewType("myprocessor"), // 配置里用的名字
createDefaultConfig, // 默认配置
processor.WithMetrics(createMetrics, component.StabilityLevelBeta),
processor.WithTraces(createTraces, component.StabilityLevelBeta),
// 没声明的信号类型 = 不支持,配了会报错
)
}
func createMetrics(ctx context.Context, set processor.Settings,
cfg component.Config, next consumer.Metrics) (processor.Metrics, error) {
// next 就是 nextConsumer —— 下游是谁,由图注入
return &myProcessor{next: next, cfg: cfg.(*Config)}, nil
}写 processor 时最该小心的:MutatesData
上一章讲过,但值得再说一遍,因为它是自定义组件最容易出的错**:**
func (p *myProcessor) Capabilities() consumer.Capabilities {
// ⚠️ 这里必须诚实!
return consumer.Capabilities{MutatesData: true} // 我会改数据
}
func (p *myProcessor) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
// 如果你在这里改了 md(哪怕只是加个属性),
// 上面就必须是 true —— 否则[扇出时](/manual/observability/collector-architecture)
// 另一条管道的数据会被你悄悄改掉
md.ResourceMetrics().At(0).Resource().Attributes().PutStr("env", "prod")
return p.next.ConsumeMetrics(ctx, md)
}申报错了的后果:另一条管道的数据莫名其妙被改——只在特定配置下出现、极难复现、且没有任何报错。
两条保命规则:
- 拿不准就填
true—— 牺牲一次复制,换正确性。这个交易永远划算。 - 或者自己先复制再改,然后填
false—— 注释里明确允许这条路:“If the processor creates a copy of the data before modifying then this flag can be safely set to false.”
OCB:组装自己的发行版
上一章说过生产该用 OCB(OpenTelemetry Collector Builder) 只打包你要的组件。它的配置就是一份清单:
# builder-config.yaml
dist:
name: my-otelcol
output_path: ./dist
receivers:
- gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.156.0
processors:
- gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.156.0
- gomod: github.com/mycompany/myprocessor v0.1.0 # ← 你自己的!
exporters:
- gomod: github.com/open-telemetry/.../prometheusremotewriteexporter v0.156.0ocb --config=builder-config.yaml # 生成 + 编译出一个定制二进制OCB 干的事其实很朴素:根据清单生成一个 Go main 包**(把各组件的 NewFactory() 注册进去),然后 go build。**
注意“你自己的组件”和官方组件在清单里是平等的**——都只是一个 Go module。** 这就是 Factory + 接口那套设计带来的红利:你的组件和官方组件享有完全相同的地位,不需要 fork Collector、不需要给上游提 PR、不需要等 release。
这个“核心 + 清单式组装”的模式很值得学:它让一个项目能同时做到“核心精简”和“生态繁荣”——和 Prometheus 用 file_sd/http_sd 做“万能逃生舱”是同一类智慧,只是 Collector 把它推到了极致:整个 Collector 就是一堆可插拔组件 + 一张把它们连起来的图。
小结与下一部分
几百个社区组件都是同一个模子:Component 接口只有 Start/Shutdown,但注释藏着两条纪律——Start() 要快速返回(否则卡住整个启动)、后台任务要自己建 context(传进来的那个只代表启动操作,Start() 返回后可能就被取消 → goroutine 莫名退出);这是个常见的 Go 陷阱,注释写这么细说明踩的人不少。Factory 是注册入口:按信号类型分别声明(配错管道在启动期就报错)、要声明 StabilityLevel(诚实的设计)、next consumer 由图注入。写 processor 最该小心 MutatesData——申报错了会让另一条管道的数据被悄悄改,极难复现;拿不准填 true,或自己先复制再改然后填 false。OCB 按清单组装自己的发行版——你的组件和官方组件地位完全平等,不用 fork、不用等 release;这个“核心 + 清单式组装”是Factory + 接口设计带来的红利,和 Prometheus 的 file_sd 逃生舱同类智慧,只是 Collector 推到了极致:整个系统就是一堆可插拔组件 + 一张连接它们的图。
Collector 讲完了。它运送的三大支柱里,我们只深入讲过指标。下一部分补上另外两根——以及把它们串起来的那些桥。👉 第 7 部分 · 追踪与日志。
参考资料
- [1] OTel Collector 源码
component/component.go:25(Component接口与 Start 的注释纪律)、processor/processor.go(NewFactory)。 - [2] OTel Collector 官方文档:Building a custom collector(OCB)、
docs/component-stability.md。 - [3] 关联Collector 架构、MutatesData、发行版。