← 中文资源

简体中文版本

Span 属性

span 属性是挂在 span 上的结构化键值元数据——用户 id、租户、缓存命中、重试次数——把 trace 从计时记录变成可查询的“发生了什么与为什么”。

阅读英文原文

概念定义

只有名字与时长的 span 是秒表;属性让它成为记录:哪个租户、缓存结果如何、第几次重试、走了哪个分支。这就是“知道慢”与“知道为什么慢”的差别。

工作机制

插桩在明确定义的点写入属性;语义约定保留命名空间——http、db、messaging、faas——并规定标准名称与类型,自定义命名空间承载领域概念。属性进入 trace 存储成为查询维度;尾采样策略也可按属性过滤。

使用场景

定义你事故时真正会查的属性:租户与用户标识、特性开关、缓存命中标志、重试计数、批次大小。在分歧出现之前用约定完成标准化。

常见误区

无治理的自由键会碎成同义词——userid、user_id、enduser.id——跨服务查询随之失效。属性越多越好是幻觉:每个高基数属性都成倍放大索引与保留成本,策展胜过堆积。完整载荷、SQL 文本、头部塞进属性会撑爆 trace 尺寸并把机密拖进保留期;PII 不经脱敏管道就记录,等于把用户数据泄漏进可观测性存储。

代码示例

import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { trace, context, SpanStatusCode } from '@opentelemetry/api';

const sdk = new NodeSDK({
  serviceName: 'orders-service',
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTLP_ENDPOINT ?? 'http://collector:4318/v1/traces',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

await sdk.start();
const tracer = trace.getTracer('orders', '1.0.0');

async function processOrder(orderId: string): Promise<void> {
  return tracer.startActiveSpan('order.process', async (span) => {
    span.setAttribute('order.id', orderId);
    try {
      await tracer.startActiveSpan('order.validate', async (child) => {
        await new Promise((r) => setTimeout(r, 10));
        child.setAttribute('validation.rules', 12);
        child.end();
      });

      await tracer.startActiveSpan('order.charge', async (child) => {
        await new Promise((r) => setTimeout(r, 25));
        child.recordException(new Error('card declined simulated'));
        child.setStatus({ code: SpanStatusCode.ERROR });
        child.end();
      });

      span.setStatus({ code: SpanStatusCode.OK });
    } catch (err) {
      span.recordException(err as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
    } finally {
      span.end();
    }
  });
}

await processOrder('ord-7');
await sdk.shutdown();
use tracing::{debug, error, info, instrument, warn};
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

#[instrument(name = "order_flow", skip(payload))]
fn process_order(order_id: u64, payload: &str) -> u64 {
    debug!(bytes = payload.len(), "payload received");
    let total = payload.len() as u64 * order_id % 97;
    info!(total, "order total computed");
    total
}

fn init_tracing() {
    let filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new("info,order_service=debug"));

    let is_prod = std::env::var("APP_ENV")
        .map(|v| v == "production")
        .unwrap_or(false);

    let registry = tracing_subscriber::registry().with(filter);

    if is_prod {
        registry
            .with(fmt::layer().json().flatten_event(true))
            .init();
    } else {
        registry
            .with(fmt::layer().pretty().with_target(false))
            .init();
    }
}

fn main() {
    init_tracing();

    let span = tracing::info_span!("startup", version = env!("CARGO_PKG_VERSION"));
    let _guard = span.enter();

    info!("service starting");
    warn!("cache warmup skipped");

    let total = process_order(42, "line-item;quantity=3");
    error!(total, "downstream settlement slow");

    drop(_guard);
    info!("shutdown complete");
}