Chapter 08

标准命名的威力

Attribute 和 Resource 如果各写各的,最后只能得到一座"数据墓地"。OTel 的 Semantic Conventions 给每个领域(HTTP、DB、RPC、FaaS、Messaging、GenAI)都定好了命名——用它你就能直接用 Jaeger/Datadog 的现成面板,不用手调任何查询。

为什么要有语义约定

同样一个字段:

Grafana 上查"error 率"时你要写三个 OR,加一个新服务要改查询。Datadog 的预置面板直接认不出来——你买了工具却用不起预设。

OTel 的药方
强制字段命名统一:上面这个字段,OTel 规定叫 http.response.status_code。全行业遵守——面板、告警、查询语言都可以开箱即用。

三个属性层级

层级附着位置举例
Resource整个 SDK 生命周期不变service.name / k8s.pod.name
ScopeTracer/Meter 实例tracer name + version
Attribute单个 span / log / metric pointhttp.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.*
service.name(必选)、service.versionservice.namespaceservice.instance.id
deployment.*
deployment.environment(prod/staging/dev)
host.*
host.namehost.idhost.typehost.archhost.image.name
os.*
os.type(linux/windows)、os.version
container.*
container.idcontainer.namecontainer.image.tags
k8s.*
k8s.cluster.name / k8s.namespace.name / k8s.pod.name / k8s.deployment.name
cloud.*
cloud.provider(aws/gcp/azure)、cloud.regioncloud.availability_zone
faas.*
serverless:faas.name / faas.version / faas.instance / faas.trigger
process.*
process.pid / process.command / process.runtime.name

resourcedetection 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 时而 int
命名空间自定义用公司前缀
业务字段用 acme.order.id,别用裸 order.id 和标准冲突
不要高基数
user.id 放 trace 可,放 metric 攻击存储;session.id / request.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 预置面板看到 QPS / 错误率 / 延迟?能 = SemConv 用对了;不能 = 埋点要整改。

本章小结