为什么要有语义约定
同样一个字段:
- A 服务写
status_code - B 服务写
http_status - C 服务写
responseCode
Grafana 上查"error 率"时你要写三个 OR,加一个新服务要改查询。Datadog 的预置面板直接认不出来——你买了工具却用不起预设。
强制字段命名统一:上面这个字段,OTel 规定叫
http.response.status_code。全行业遵守——面板、告警、查询语言都可以开箱即用。
三个属性层级
| 层级 | 附着位置 | 举例 |
|---|---|---|
| Resource | 整个 SDK 生命周期不变 | service.name / k8s.pod.name |
| Scope | Tracer/Meter 实例 | tracer name + version |
| Attribute | 单个 span / log / metric point | http.request.method |
资源级别 = "我是谁",scope = "哪个模块发的",attribute = "这一条记录的细节"。后端通常支持按任意级别过滤/聚合。
Resource 语义约定
import { Resource } from "@opentelemetry/resources"; import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION, SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, } from "@opentelemetry/semantic-conventions"; new Resource({ [SEMRESATTRS_SERVICE_NAME]: "order-service", [SEMRESATTRS_SERVICE_VERSION]: "1.2.0", [SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: "prod", "service.namespace": "commerce", "service.instance.id": process.env.HOSTNAME, });
强制字段只有一个:service.name。其他强烈建议。
常见 Resource 字段
service.name(必选)、service.version、service.namespace、service.instance.iddeployment.environment(prod/staging/dev)host.name、host.id、host.type、host.arch、host.image.nameos.type(linux/windows)、os.versioncontainer.id、container.name、container.image.tagsk8s.cluster.name / k8s.namespace.name / k8s.pod.name / k8s.deployment.namecloud.provider(aws/gcp/azure)、cloud.region、cloud.availability_zonefaas.name / faas.version / faas.instance / faas.triggerprocess.pid / process.command / process.runtime.nameresourcedetection processor
大多数 resource 字段不用手填—— Collector 的 resourcedetection processor 自动探测:
processors:
resourcedetection:
detectors:
- env # 读 OTEL_RESOURCE_ATTRIBUTES 环境变量
- system # host.name / os.*
- docker # container.id
- k8s # k8s.* 通过 downward API
- ec2 # AWS 元数据服务
- gcp
- aks
override: false # 已有值不覆盖(应用层优先)
HTTP 语义约定
{
"http.request.method": "GET",
"http.route": "/users/:id",
"http.response.status_code": 200,
"http.request.body.size": 0,
"http.response.body.size": 1234,
"url.full": "https://api.example.com/users/42",
"url.scheme": "https",
"url.path": "/users/42",
"server.address": "api.example.com",
"server.port": 443,
"client.address": "203.0.113.5",
"user_agent.original": "curl/8.1"
}
·
url.path 是具体值:/users/42(每个用户不同)·
http.route 是路由模板:/users/:id(用来聚合)分位数按 http.route 聚合才有意义,否则每个 userId 一条序列,基数爆炸。
DB 语义约定
{
"db.system": "postgresql",
"db.namespace": "orders_prod", // database name
"db.query.text": "SELECT * FROM orders WHERE id = $1",
"db.operation.name": "SELECT",
"db.collection.name": "orders",
"server.address": "pg-primary.internal",
"server.port": 5432
}
db.query.text 要参数化——别把真实值拼进去(SELECT * FROM users WHERE id = 42),会造成基数爆炸 + 敏感数据泄露。用 $1 / ? 占位。
RPC / gRPC
{
"rpc.system": "grpc",
"rpc.service": "orders.v1.OrderService",
"rpc.method": "PlaceOrder",
"rpc.grpc.status_code": 0,
"server.address": "orders.internal",
"server.port": 50051
}
Span 的 name 推荐用 {service}/{method} 格式。
Messaging
{
"messaging.system": "kafka",
"messaging.destination.name": "order.created",
"messaging.operation": "publish", // publish / receive / process
"messaging.message.id": "msg_abc",
"messaging.kafka.partition": 3,
"messaging.kafka.offset": 12345
}
GenAI 语义约定(2025 Stable)
随着 LLM 应用崛起,OTel 加了 GenAI 一大块——和 OpenLLMetry / Langfuse 对齐:
{
"gen_ai.system": "openai",
"gen_ai.request.model": "gpt-4o",
"gen_ai.response.model": "gpt-4o-2025-10-01",
"gen_ai.operation.name": "chat", // chat / text_completion / embeddings
"gen_ai.request.temperature": 0.7,
"gen_ai.request.max_tokens": 2000,
"gen_ai.usage.input_tokens": 532,
"gen_ai.usage.output_tokens": 187,
"gen_ai.response.finish_reasons": ["stop"]
}
Agent / Tool 调用:
{
"gen_ai.agent.name": "research-agent",
"gen_ai.tool.name": "web_search",
"gen_ai.tool.call.id": "call_123",
"gen_ai.conversation.id": "conv_456"
}
这些字段让 Datadog、Langfuse、Phoenix 等 LLM 观测工具拿到 OTel 数据就能直接画出 token 用量、成本、tool 调用链。一次埋点,所有后端认。
错误
{
"error.type": "CardDeclined",
"exception.type": "PaymentError",
"exception.message": "insufficient funds",
"exception.stacktrace": "..."
}
error.type 是粗粒度分类(适合聚合),exception.* 是具体堆栈——一起用。
Attribute 命名规则
user.id(✓) vs user_id(×) vs userId(×)http.method(✓) vs HTTP.Method(×)http.status_code 始终是 int,不要时而 string 时而 intacme.order.id,别用裸 order.id 和标准冲突SemConv 版本化
SemConv 有版本,随时间演进(早期 http.method → 后来 http.request.method)。SDK 的 semantic-conventions 包会把常量 export 出来——升级包可能要改几个 attribute 名:
// 老 import { SemanticAttributes } from "@opentelemetry/semantic-conventions"; span.setAttribute(SemanticAttributes.HTTP_METHOD, "GET"); // 新(SemConv 1.25+) import { ATTR_HTTP_REQUEST_METHOD } from "@opentelemetry/semantic-conventions"; span.setAttribute(ATTR_HTTP_REQUEST_METHOD, "GET");
Collector 的 schema_url 能做版本转换——老 SDK 产 http.method,Collector 可以转成 http.request.method 给新后端。
自定义业务 attribute
标准字段不够用,你的业务字段怎么办?
span.setAttributes({ // 标准字段 "http.route": "/checkout", "user.id": userId, // 业务字段:用公司命名空间前缀 "acme.order.id": orderId, "acme.order.total_cents": total, "acme.order.currency": "USD", "acme.order.item_count": items.length, "acme.cart.experiment": "v2", });
acme 是公司前缀(类似 Java 包名倒写)。这样和 OTel 标准字段不冲突,后端也能一眼区分"标准 vs 业务"。
好处具象化
语义约定用对了:
- Grafana 导入 OTel 预置 dashboard——
http.route维度的 RED 图直接出 - Datadog APM service map 自动画——它认识
service.name和peer.service - Honeycomb BubbleUp / Jaeger 瀑布图、timeline——全靠标准字段
- 告警规则跨团队复用:
sum(rate(span{http.response.status_code=~"5.."}))在所有服务上都能跑
一个新服务上线后,运维能不能不问开发、光靠 Grafana OTel 预置面板看到 QPS / 错误率 / 延迟?能 = SemConv 用对了;不能 = 埋点要整改。
本章小结
- 语义约定让全行业对齐字段命名——预置面板、通用告警、跨工具导出
- Resource 描述"是谁",自动探测器(Collector
resourcedetection)负责 - 按领域各有规范:HTTP / DB / RPC / Messaging / FaaS / GenAI(2025 Stable)
- 命名规则:小写、点号、类型稳定、加公司前缀避冲突
- 高基数字段(user.id / request.id)只进 trace,不进 metric
- Span 用 http.route 聚合,url.path 留原始值