从 YAML 到产物 — Weaver 编译、Rego 策略、schema 迁移
本章讲机器:同一份 YAML 怎么变成人读文档、各语言代码,以及构建期有哪些闸门保证「改约定不闯祸」。三件事:① Weaver 生成,② Rego 策略,③ schema 版本迁移。
3.1 Weaver:唯一的编译器
YAML 本身不会自己变成文档。干这活的是 Weaver——一个外部工具,以固定版本的 Docker 容器引入(otel/weaver:v0.24.2,dependencies.Dockerfile:6)。本仓只调用它,不含它的源码。
Weaver 在本仓有两个主要用法(都在 Makefile):
| make 目标 | Weaver 子命令 | 产出 |
|---|---|---|
table-generation | registry update-markdown | 把字段表回填进 docs/**/*.md 里的标记区块(Makefile:164-178) |
registry-generation | registry generate markdown | 生成 docs/registry/ 下的属性注册表文档(Makefile:185-197) |
两者都把 model/(源)、templates/(模板)、docs/(目标)挂进容器。心智模型:
model/**/*.yaml ─┐
templates/** ├─▶ [ weaver 容器 ] ─▶ docs/**/*.md (表格被替换/生成)
│ ─▶ (下游仓:各语言常量代码)
关键设计——文档不是手写的,是生成的。 所以有个配套校验 table-check:用 --dry-run 跑同样的生成,如果结果和已提交的 docs 不一致就报错(Makefile:199-214)。这保证「YAML 改了但忘了重生成文档」会被 CI 抓住。各语言 SDK 同样用 Weaver 把这份 YAML 生成本语言常量,这就是「一处定义、全栈一致」的兑现方式。
3.2 Rego 策略:构建期的兼容性闸门
光生成还不够——改约定不能破坏已经发布的契约。这由 policies/*.rego(开放策略代理 OPA 的规则语言)在 make check-policies 时强制执行(Makefile:287-300)。
最关键的一行是它和谁比:
# Makefile:298-299 (check-policies 内)
--baseline-registry=https://github.com/open-telemetry/semantic-conventions/archive/refs/tags/v$(LATEST_RELEASED_SEMCONV_VERSION).zip[model]
--policy=/home/weaver/policies
它下载最新发布版当 baseline,把当前 model 和它对比。policies/compatibility.rego 就建立在「baseline vs 当前」两套集合上做差异检查(policies/compatibility.rego:13-30,baseline_attributes / registry_attributes)。意图很明确:稳定字段不许被删或改类型,否则下游已经依赖它的代码会崩。
策略文件按关切分工(policies/ 目录):
| 策略文件 | 把关什么 |
|---|---|
compatibility.rego | 向后兼容:稳定字段不许破坏性变更 |
deprecation.rego | 弃用/改名规则自洽(下一节细讲) |
attribute_name_collisions.rego / constant_name_collisions.rego | 名字不许撞 |
attribute_types.rego | 类型合法 |
metric_brief_format.rego / metrics_collisions.rego | metric 命名与说明规范 |
group_stability.rego / entity_stability.rego | 稳定性标记一致 |
3.3 弃用与改名的策略自洽(deprecation.rego)
policies/deprecation.rego 是个很好的「策略即代码」样本。它检查一类错误:你把字段改名了,但新名字根本不存在或也被弃用了。
# policies/deprecation.rego:31-40 (简化解读)
deny contains ... if {
group := input.groups[_]
startswith(group.id, "registry.") # 只查 registry 字段
attr := group.attributes[_]
attr.deprecated.renamed_to != null # 这字段声明改名了
not attr.deprecated.renamed_to in registry_attribute_names # 但新名不在「未弃用字段」集合里
# → 报错:renamed_to 指向了不存在/已弃用的字段
}
它还逐条校验改名前后类型要兼容:string→string、int→int、甚至 string → enum-of-strings 都允许(deprecation.rego:59-73),但 string → enum-of-ints 就拒。同样的检查覆盖 metric 改名(:161-170)和 event 改名(:172-181)。
为什么重要: 弃用不是删除,而是带迁移路径的退休。策略保证每条 renamed_to 都真的指向一个可用的新家,数据迁移才不会断链。
3.4 schema 版本文件:让改名可被机器执行
策略保证「改名声明自洽」,但运行时的老数据怎么自动升级到新名字?靠 schemas/<version> 文件——每个发布版一个,记录从上一版到本版的字段映射。
看 gen_ai token 改名的真实记录:
# schemas/1.27.0:29-30 (节选)
changes:
- rename_attributes:
attribute_map:
gen_ai.usage.completion_tokens: gen_ai.usage.output_tokens
gen_ai.usage.prompt_tokens: gen_ai.usage.input_tokens
这条映射让任何遥测处理 器都能把老字段 gen_ai.usage.prompt_tokens 自动重写成新字段 gen_ai.usage.input_tokens——无需改采集端代码。schema 文件按版本号目录排列,从 1.4.0 一直到 1.42.0(schemas/ 目录),构成一条完整的迁移链。
三件事如何咬合成闭环:
改 YAML(声明 deprecated.renamed_to)
│
├─▶ check-policies: deprecation.rego 确认新名存在且类型兼容
│
├─▶ table-generation: 文档同步反映改名
│
└─▶ schemas/<新版>: 写 rename_attributes,老数据可自动迁移
声明、校验、文档、迁移四步对齐,一个字段才算「合规地改了名」。
3.5 代码地图
| 主题 | 文件 | 符号/目标名 |
|---|---|---|
| Weaver 容器版本 | dependencies.Dockerfile | FROM otel/weaver:v0.24.2 |
| 文档生成 | Makefile | table-generation, registry-generation |
| 文档与 YAML 一致性校验 | Makefile | table-check(--dry-run) |
| 兼容性闸门 + baseline 对比 | Makefile | check-policies |
| 兼容性规则 | policies/compatibility.rego | baseline_attributes, registry_attributes |
| 弃用/改名自洽规则 | policies/deprecation.rego | deny ... renamed_to 规则族 |
| 版本改名映射 | schemas/1.27.0 | rename_attributes.attribute_map |