在 OpenTelemetry 的追踪、日志和指标中设置自定义属性
一个 SigNoz 工作区会接收来自众多应用、团队和环境的遥测数据。自定义属性是区分这些遥测数据的关键值对。添加诸如 team.name 或 tenant.id 等属性后,你就可以在探索器中按此筛选,基于此对图表进行分组,并将其用作告警条件。
你可以在环境变量、应用代码或 OpenTelemetry Collector 中设置属性。本指南涵盖这三种方式,适用于链路(traces)、日志(logs)和指标(metrics)。
属性与资源属性
OpenTelemetry 有两种属性类型。在编写任何配置之前,请先确定你需要哪种类型。
| 资源属性 | 属性 | |
|---|---|---|
| 描述对象 | 产生遥测数据的进程 | 单个 Span、指标数据点或日志记录 |
| 值变化频率 | 每进程一次,在启动时 | 每次请求或操作时 |
| 作用范围 | 该进程发出的所有 Span、指标和日志记录 | 仅你设置它的那个特定项 |
| 示例 | service.name, deployment.environment.name, host.name, team.name | http.request.method, db.query.text, tenant.id |
使用资源属性来回答“这来自哪里”,使用属性来回答“这次操作中发生了什么”。
Info如果存在对应项,请优先使用 OpenTelemetry 语义约定中定义的名称。SigNoz 基于这些标准名称构建服务地图、APM 图表和基础设施视图。仅对于语义约定未覆盖的概念,才使用自定义名称,例如 team.name 或 tenant.id。
前置条件
- 一个已集成 OpenTelemetry 的应用。请参阅适用于你编程语言的 集成指南。
- 一个 SigNoz 账户,无论是 SigNoz Cloud 还是 自托管 部署。
- 一个 OpenTelemetry Collector,如果你计划在应用外部设置属性。请参阅 Collector 配置。
选择设置属性的位置
| 方法 | 适用场景 | 设置对象 |
|---|---|---|
| 环境变量 | 希望整个进程使用同一个值,且不想修改代码 | Resource attributes |
| SDK 代码 | 值来自应用配置,或使用按请求变化的 attribute | Resource attributes 和 attributes |
| Collector | 无法修改应用,或希望为多个服务应用同一规则 | Resource attributes 和 attributes |
优先使用环境变量。它无需改动代码,且所有 OpenTelemetry SDK 都能读取。
通过环境变量设置 resource attributes
在运行应用所在进程中设置以下变量,然后重启应用:
Copyexport OTEL_SERVICE_NAME="checkout"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=production,service.version=1.4.2,team.name=payments"
OTEL_RESOURCE_ATTRIBUTES 接收逗号分隔的 key=value 对。若 key 或 value 中包含逗号或等号,需进行 percent-encode。
OTEL_SERVICE_NAME 用于设置 service.name resource attribute。若两个变量同时设置了 service.name,以 OTEL_SERVICE_NAME 为准。
OpenTelemetry 已弃用 deployment.environment,改用 deployment.environment.name。新配置中请使用新名称。详情参见 deployment attribute registry。
SigNoz 支持的环境变量完整列表,请参见 OpenTelemetry environment variables。
在 SDK 中设置 resource attributes
当值来自应用配置时,应在代码中设置 resource attributes。若同一 key 也出现在 OTEL_RESOURCE_ATTRIBUTES 中,优先级取决于编程语言。各标签页中说明了具体顺序。
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { defaultResource, resourceFromAttributes } = require('@opentelemetry/resources')
const {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} = require('@opentelemetry/semantic-conventions')
const sdk = new NodeSDK({
resource: defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'checkout',
[ATTR_SERVICE_VERSION]: '1.4.2',
'deployment.environment.name': 'production',
'team.name': 'payments',
})
),
})
sdk.start()resourceFromAttributes 要求 @opentelemetry/resources 2.0.0 或更高版本。在 1.x 版本中,这个包导出的是 Resource 类,而且 1.x 的类和 SemanticResourceAttributes 常量都已被弃用。
NodeSDK 会用你传入的 resource 替换默认 resource,所以要合并 defaultResource(),以保留 service.name 和 telemetry.sdk.* 属性。
NodeSDK 随后会把检测到的 resource 合并到你的 resource 上,而默认检测器会读取 OTEL_RESOURCE_ATTRIBUTES。因此这个环境变量里的同名键会覆盖代码中的同名键。要么从变量中删掉该键,要么传入 resourceDetectors: [] 关闭检测。
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry import trace
resource = Resource.create({
"service.name": "checkout",
"service.version": "1.4.2",
"deployment.environment.name": "production",
"team.name": "payments",
})
trace.set_tracer_provider(TracerProvider(resource=resource))把同一个 resource 对象也传给 LoggerProvider 和 MeterProvider,这样日志和指标就能携带相同的属性。
Resource.create 会先读取 OTEL_RESOURCE_ATTRIBUTES,再应用你传入的属性,所以这里的值会覆盖环境变量中的值。
opentelemetry-semantic-conventions 包自 1.25.0 版本起已弃用 ResourceAttributes 类,请像上面这样直接用普通字符串写属性名。
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ServiceAttributes;
Resource resource = Resource.getDefault().toBuilder()
.put(ServiceAttributes.SERVICE_NAME, "checkout")
.put(ServiceAttributes.SERVICE_VERSION, "1.4.2")
.put("deployment.environment.name", "production")
.put("team.name", "payments")
.build();
将 resource 传递给 SdkTracerProvider、SdkMeterProvider 和 SdkLoggerProvider。
此资源配置不会读取 OTEL_RESOURCE_ATTRIBUTES。只有 autoconfigure 模块和 Java agent 才会读取该变量。
若运行 Java agent,agent 会自动构建资源。此时应使用 OTEL_RESOURCE_ATTRIBUTES 替代上述代码。
import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
res, err := resource.Merge(
resource.Default(),
resource.NewSchemaless(
semconv.ServiceName("checkout"),
semconv.ServiceVersion("1.4.2"),
semconv.DeploymentEnvironmentNameKey.String("production"),
attribute.String("team.name", "payments"),
),
)
通过 WithResource 选项将 res 传递给 tracer、meter 和 logger provider。
semconv 的导入路径必须对应你当前 go.opentelemetry.io/otel 版本中实际存在的包。semconv/v1.40.0 从 v1.42.0 开始提供。在改用更新的路径前,请先检查对应版本标签下的 semconv 目录。
resource.NewSchemaless 会保留 resource.Default() 的 schema URL。如果使用 resource.NewWithAttributes 且其 schema URL 不匹配,resource.Merge 将返回 ErrSchemaURLConflict 并丢弃 schema URL。在两种情况下都需处理该错误。
resource.Default() 会读取 OTEL_RESOURCE_ATTRIBUTES,而 resource.Merge 优先采用第二个 resource。因此,此处定义的属性值会覆盖环境变量。
ConfigureResource
配置资源对象时,将资源属性添加到 builder。这里的值会覆盖 OTEL_RESOURCE_ATTRIBUTES 环境变量中相同的键值对。
为单个 span、指标或日志记录添加属性
当属性值随每个请求变化时,例如租户 ID 或客户计划,可以使用单个属性。
Span 属性
将属性添加到 instrumentation 已经创建并开启的 span 中:
Node.jsPythonJavaGo.NETCopyconst { trace } = require('@opentelemetry/api')
trace.getActiveSpan()?.setAttribute('tenant.id', tenantId)Copy
from opentelemetry import trace
trace.get_current_span().set_attribute("tenant.id", tenant_id)Copy
import io.opentelemetry.api.trace.Span;
Span.current().setAttribute("tenant.id", tenantId);Copy
import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
trace.SpanFromContext(ctx).SetAttributes(attribute.String("tenant.id", tenantID))Copy
using System.Diagnostics;
Activity.Current?.SetTag("tenant.id", tenantId);
指标属性
记录测量值时传入属性:
Node.jsPythonJavaGo.NETCopyorderCounter.add(1, { 'tenant.id': tenantId, 'order.type': orderType })Copy
order_counter.add(1, {"tenant.id": tenant_id, "order.type": order_type})Copy
import io.opentelemetry.api.common.Attributes;
orderCounter.add(1, Attributes.builder()
.put("tenant.id", tenantId)
.put("order.type", orderType)
.build());
复制import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
orderCounter.Add(ctx, 1, metric.WithAttributes(
attribute.String("tenant.id", tenantID),
attribute.String("order.type", orderType),
))复制orderCounter.Add(1,
new KeyValuePair<string, object?>("tenant.id", tenantId),
new KeyValuePair<string, object?>("order.type", orderType));