新的追踪 API

了解 Sentry JavaScript SDK 8.x 中的新追踪 API

SDK 8.x 版本引入了新的性能监控 API。这些 API 旨在提供对如何收集和报告性能数据的更多控制。

这些 API 在 7.x 中也已提供,因此您可以在升级到 8.x 之前逐步迁移到新 API。

以前,有两个关键 API 用于为应用程序添加手动性能监控:

  • startTransaction()
  • span.startChild()

这些 API 基于 Sentry 的原始数据模型。该模型基于一个根事务(Transaction),它可以包含一个嵌套的 Span 树。

在新模型中,事务的概念已经消失。现在,所有操作都是在 span 上进行的,无论它们在树中的位置如何。请注意,span 仍然可以在 Sentry UI 中被分组为一个事务。但是,这在后台发生,从 SDK 的角度来看,您只需要关注 span。

新模型在手动启动或结束时不再区分事务和 span。相反,您始终使用相同的 API 来启动一个新的 span。这将根据当前活动的 span 自动创建一个新的根 Span(没有父级的常规 span,概念上类似于事务)或子 Span。

有三个关键 API 可用于启动 span:

  • startSpan()
  • startSpanManual()
  • startInactiveSpan()

所有三个 span API 都接受 StartSpanOptions 作为第一个参数,其结构如下:

Copied
interface StartSpanOptions {
  // The only required field - the name of the span
  name: string;
  attributes?: SpanAttributes;
  op?: string;
  scope?: Scope;
  forceTransaction?: boolean;
}

这是在大多数情况下应使用的最常见的 API。它将启动一个新的 span,在给定回调的持续时间内使其成为活动 span,并在回调结束时自动结束该 span。您可以这样使用它:

Copied
Sentry.startSpan(
  {
    name: "my-span",
    attributes: {
      attr1: "my-attribute",
      attr2: 123,
    },
  },
  (span) => {
    // do something that you want to measure
    // once this is done, the span is automatically ended
  }
);

您也可以传递一个异步函数:

Copied
Sentry.startSpan(
  {
    name: "my-span",
    attributes: {},
  },
  async (span) => {
    // do something that you want to measure
    await waitOnSomething();
    // once this is done, the span is automatically ended
  }
);

由于 startSpan() 会将创建的 span 设置为活动 span,任何在回调中创建 span 的自动或手动 instrumentation 都会将新 span 作为我们刚刚启动的 span 的子项附加。

请注意,如果在回调中抛出错误,span 状态将自动设置为错误状态。

这是 startSpan() 的一个变体,唯一的区别是它不会在回调结束时自动结束 span。使用此方法时,您需要自己调用 span.end()

Copied
Sentry.startSpanManual(
  {
    name: "my-span",
  },
  (span) => {
    // do something that you want to measure

    // Now manually end the span ourselves
    span.end();
  }
);

在大多数情况下,startSpan() 应该能满足您手动 instrumentation 的所有需求。但如果您发现自己处于某种情况下,自动结束 span 无法满足您的需求,您可以改用 startSpanManual()

此函数也会将创建的 span 设置为回调期间的活动 span,并且如果回调内抛出错误,它也会将 span 状态更新为错误状态。

与另外两种方法不同,此方法不接受回调,也不会将创建的 span 设置为活动 span。如果您想创建不需要有任何子项的独立 span,可以使用此方法:

Copied
Sentry.startSpan({ name: "outer" }, () => {
  const inner1 = Sentry.startInactiveSpan({ name: "inner1" });
  const inner2 = Sentry.startInactiveSpan({ name: "inner2" });

  // do something

  // manually end the spans
  inner1.end();
  inner2.end();
});

不会有任何 span 作为非活动 span 的子 span 创建。

您可以使用 withActiveSpan 辅助函数来创建作为特定 span 子项的 span:

Copied
Sentry.withActiveSpan(parentSpan, () => {
  Sentry.startSpan({ name: "my-span" }, (span) => {
    // span will be a direct child of parentSpan
  });
});

虽然在大多数情况下,您不需要区分创建 span 还是事务(只需调用 startSpan(),我们会自动处理),但有时您可能需要确保创建一个事务(例如,如果您需要在 Sentry UI 中将其视为事务)。对于这些情况,您可以传递 forceTransaction: true 到启动 span 的 API,例如:

Copied
const transaction = Sentry.startInactiveSpan({
  name: "transaction",
  forceTransaction: true,
});

以前,span 和事务有许多属性和方法可以使用。大多数这些属性和方法已被移除,以支持更简洁、更直接的 API,该 API 也与 OpenTelemetry Spans 对齐。您可以参考下表,了解以前存在的内容以及如何映射到新的 API:

spanToJSONgetRootSpansetHttpStatusspanToTraceHeaderspanToTraceContext 均由 SDK 导出以供使用。

旧名称替换为
span.traceIdspan.spanContext().traceId
span.spanIdspan.spanContext().spanId
span.parentSpanIdspanToJSON(span).parent_span_id
span.statusspanToJSON(span).status
span.sampledspanIsSampled(span)
span.startTimestampspan.startTime - 注意格式不同!
span.tags使用属性或在作用域中设置标签
span.dataspanToJSON(span).data
span.transactiongetRootSpan(span)
span.instrumenter已移除
span.finish()span.end()
span.end()相同
span.setTag()span.setAttribute() 或在作用域中设置标签
span.setData()span.setAttribute()
span.setStatus()span.setStatus - 注意签名不同
span.setHttpStatus()setHttpStatus(span, status)
span.setName()span.updateName()
span.startChild()独立调用 Sentry.startSpan()
span.isSuccess()spanToJSON(span).status === 'ok'
span.toTraceparent()spanToTraceHeader(span)
span.toContext()已移除
span.updateWithContext()已移除
span.getTraceContext()spanToTraceContext(span)

此外,事务有以下 API:

旧名称替换为
namespanToJSON(span).description
trimEnd已移除
parentSampledspanIsSampled(span) & spanContext().isRemote
metadata使用属性代替或在作用域中设置
setContext()在作用域中设置上下文
setMeasurement()Sentry.setMeasurement()
setMetadata()使用属性代替或在作用域中设置
getDynamicSamplingContextgetDynamicSamplingContextFromSpan(span)

以前的模型中有 DataTagsContext 的概念,它们可以用于不同的目的。然而,这有两个主要缺点:首先,在给定情况下并不总是清楚应该使用哪个概念。其次,这些概念在事务或 span 中的显示方式不同。

为了解决这些问题,新模型只保留了可以在 span 上设置的 Attributes。大致来说,它们对应于以前的 Data。

如果您仍然需要设置标签或上下文,可以

Copied
Sentry.withScope((scope) => {
  scope.setTag("my-tag", "tag-value");
  Sentry.startSpan({ name: "my-span" }, (span) => {
    // do something here
    // span will have the tags from the containing scope
  });
});

除了整体更改性能 API 之外,还有一些较小的变更。

目前,tracesSampler() 可以接收作为参数传递的任意 SamplingContext。虽然这个上下文的具体结构未在任何地方详细定义,但在 v8 中其结构将发生变化。从现在起,它主要会接收 span 的属性以及一些其他相关数据。像 req(用于基于 Node 的 SDK)或 location(用于浏览器追踪)这样的属性,以前有时会传递,但以后将不再传递。

在 v7 中,如果禁用了追踪或 span 未被采样,性能 API startSpan() / startInactiveSpan() / startSpanManual() 接收到的 span 可能是 undefined

在 v8 中,为了与 OpenTelemetry 对齐,这些方法将始终返回一个 span——但该 span 可能是一个 Noop-Span,即永远不会发送的 span。这意味着您不再需要在代码的每个地方检查 span 是否存在:

Copied
Sentry.startSpan((span: Span | undefined) => {
  // previously, in order to be type safe, you had to use optional chaining or similar
  span?.setAttribute("attr", 1);
});

// In v8, the signature changes to:
Sentry.startSpan((span: Span) => {
  // no need to guard anymore!
  span.setAttribute("attr", 1);
});

在 v7 中,您可以通过在调用 startTransaction 时设置 sampled 选项来强制执行正面或负面的采样决策。这将有效地覆盖特定事务的 采样配置

在 v8 中,为了与 OpenTelemetry 对齐,sampled 选项已被移除。您仍然可以通过在 Sentry.init 中定义 tracesSampler 回调,并为特定 span 返回 10 来强制决策:

Copied
// v7
Sentry.startTransition({op: 'function.myFunction', sampled: true});

// v8
// 1. define a tracesSampler
Sentry.init({
  tracesSampler: (samplingContext) => {
    // force a positive sampling decision for a specific span
    if (samplingContext.op === 'function.myFunction') {
      return 1;
    }
    // force a negative sampling decision for a specific span
    if (samplingContext.op === 'function.healthCheck') {
      return 0;
    }
    // return 0.1 as a default sample rate for all other spans
    return 0.1;
  }
});

// 2. start the span
Sentry.startSpan({op: 'function.myFunction'}, {/*...*/});