自定义 instrumentation

了解如何捕获应用程序中任何操作的性能数据。

要捕获符合您组织需求的事务和跨度,您必须首先 设置跟踪。

要向应用程序添加自定义性能数据,您需要以跨度的形式添加自定义 instrumentation。跨度是衡量特定操作所需时间的一种方式。例如,您可以创建一个跨度来测量函数执行所需的时间。

要开始,请导入 SDK。

Copied
import * as Sentry from "@sentry/react-native";

创建跨度有三个关键函数:

  • startSpan:创建一个新的活动跨度,该跨度会自动结束。您可能会想使用这个函数。
  • startSpanManual:创建一个新的活动跨度,但需要手动结束。
  • startInactiveSpan:创建一个新的非活动跨度,且需要手动结束。

如果在启动新跨度时已经有当前活动的跨度,则新跨度将自动作为当前活动跨度的子跨度启动。这意味着,如果一个跨度作为 活动跨度 启动,它将成为父跨度,并且在此回调中创建的任何跨度都将作为该父跨度的子跨度。如果有父跨度,错误将与父跨度关联。

相比之下,非活动跨度 不会自动关联任何子跨度。如果您不关心捕获子活动,这非常有用。

活动跨度的一个关键约束是它们只能在回调内部激活。此约束存在的原因是,在处理异步代码时,否则无法将子跨度与正确的父跨度关联。

如果您无法将执行代码包装在回调中(例如,在使用钩子或其他类似功能时),您需要使用非活动跨度,并可以结合使用 withActiveSpan 手动将子跨度与正确的父跨度关联。

在浏览器和移动环境中,默认情况下,所有跨度都收集在一个扁平的层次结构中,每个跨度都是根跨度的直接子跨度。

保持扁平层次结构的关键原因是,如果多个异步操作并行启动,则无法确定哪个跨度是哪个子跨度的父跨度。想象以下例子:

Copied
Sentry.startSpan({ name: "span 1" }, async () => {
  await fetch("https://example.com/1");
  await fetch("https://example.com/2");
  await fetch("https://example.com/3");
});

Sentry.startSpan({ name: "span 2" }, async () => {
  await fetch("https://example.com/4");
  await fetch("https://example.com/5");
  await fetch("https://example.com/6");
});

在浏览器中,无法知道 span 1 仅在其回调函数内活动,而 span 2 在另一个回调函数中活动。如果不使用扁平层次结构,所有 fetch 跨度都会成为 span 2 的子跨度。这将导致误导和混淆,因此我们设计为在浏览器中,默认情况下 所有跨度都成为根跨度的子跨度(根跨度通常是页面加载或导航跨度)。这确保了您始终拥有一个扁平的跨度层次结构。

这是我们在确保捕获的数据准确和可靠方面所做的权衡。如果您需要捕获更复杂的跨度层次结构,可以通过设置 parentSpanIsAlwaysRootSpan: false 来退出此行为:

Copied
Sentry.init({
  parentSpanIsAlwaysRootSpan: false,
});

如果您选择恢复到使用完整的层次结构行为,其中跨度是当前活动跨度的子跨度,则必须确保没有多个并行的异步操作启动跨度。否则,您可能会得到不正确的数据。

以下选项可以用于所有启动跨度的函数:

选项类型描述
namestring跨度的名称。
opstring跨度的操作类型。
startTimenumber跨度的开始时间。
attributesRecord<string, Primitive>要附加到跨度的属性。
parentSpanSpan如果设置,将使该跨度成为指定跨度的子跨度。否则,该跨度将成为当前活动跨度的子跨度。
onlyIfParentboolean如果为 true,在没有活动父跨度时忽略该跨度。
forceTransactionboolean如果为 true,确保此跨度在 Sentry UI 中显示为事务。

唯一必需的选项是 name,其他选项均为可选。

对于大多数场景,我们建议使用 Sentry.startSpan() 启动活动跨度。这将在提供的回调中启动一个新的活动跨度,并在回调完成后自动结束该跨度。回调可以是同步的,也可以是异步的(Promise)。如果是异步回调,当 Promise 解析或拒绝时,跨度将结束。如果提供的回调抛出错误或被拒绝,跨度将被标记为失败。

为同步操作启动一个跨度:

Copied
const result = Sentry.startSpan({ name: "Important Function" }, () => {
  return expensiveFunction();
});

为异步操作启动一个跨度:

Copied
const result = await Sentry.startSpan(
  { name: "Important Function" },
  async () => {
    const res = await doSomethingAsync();
    return updateRes(res);
  },
);

您还可以嵌套跨度:

Copied
const result = await Sentry.startSpan(
  {
    name: "Important Function",
  },
  async () => {
    const res = await Sentry.startSpan({ name: "Child Span" }, () => {
      return expensiveAsyncFunction();
    });

    return updateRes(res);
  },
);

有时您不希望跨度在回调完成后自动结束。在这种情况下,您可以使用 Sentry.startSpanManual()。这将在提供的回调中启动一个新的活动跨度,但在回调完成后不会自动结束。您需要通过调用 span.end() 手动结束该跨度。

Copied
// Start a span that tracks the duration of middleware
function middleware(_req, res, next) {
  return Sentry.startSpanManual({ name: "middleware" }, (span) => {
    res.once("finish", () => {
      span.setHttpStatus(res.status);
      // manually tell the span when to end
      span.end();
    });
    return next();
  });
}

要添加非活动的独立跨度,您可以创建独立的跨度。这在您有多个工作项需要归类到一个父跨度下,但又与当前活动跨度无关时非常有用。然而,在大多数情况下,您可能更希望创建并使用上述的 startSpan API。

Copied
const span1 = Sentry.startInactiveSpan({ name: "span1" });

someWork();

span1.end();

默认情况下,任何启动的跨度都将成为当前活动跨度的子跨度。如果您希望有不同的行为,可以通过指定 parentSpan 选项来强制跨度成为特定父跨度的子跨度。例如:

Copied
const parentSpan = Sentry.startInactiveSpan({ name: "Parent Span" });
const childSpan = Sentry.startInactiveSpan({ name: "Child Span", parentSpan });

childSpan.end();
parentSpan.end();

此选项也适用于 startSpanstartSpanManual

我们提供了一些有助于自定义 instrumentation 的实用工具。

返回当前活动的跨度。

Copied
const activeSpan = Sentry.getActiveSpan();

返回给定跨度的根跨度。如果该跨度已经是根跨度,则返回该跨度本身。

Copied
const activeSpan = Sentry.getActiveSpan();
const rootSpan = activeSpan ? Sentry.getRootSpan(activeSpan) : undefined;

此方法允许您在回调的持续时间内使一个跨度处于活动状态。您可以将其与 startInactiveSpan 结合使用,以手动将子跨度与正确的父跨度关联:

Copied
const span = Sentry.startInactiveSpan({ name: "Parent Span" });

Sentry.withActiveSpan(span, () => {
  // `span` is now active, any other spans will be children of it
  Sentry.startSpan({ name: "Child Span" }, () => {
    // Do something
  });
});

您还可以将 null 传递给 withActiveSpan,以确保跨度没有父跨度:

Copied
Sentry.withActiveSpan(null, () => {
  // This will not have a parent span, no matter what
  Sentry.startSpan({ name: "Parent Span" }, () => {
    // Do something
  });
});

或者,您可以使用 parentSpan 选项来实现相同的效果:

Copied
const span = Sentry.startInactiveSpan({ name: "Parent Span" });
const childSpan = Sentry.startInactiveSpan({
  name: "Child Span",
  parentSpan: span,
});

在回调的持续时间内抑制采样跨度的创建。这在您希望防止某些跨度被捕获时非常有用。例如,如果您不希望为某个 fetch 请求创建跨度,可以这样做:

Copied
Sentry.suppressTracing(() => {
  fetch("https://example.com");
});

您可以与跨度一起捕获跨度属性。跨度属性可以是以下类型:stringnumberboolean,以及这些类型的(非混合)数组。您可以在启动跨度时指定属性:

Copied
Sentry.startSpan(
  {
    attributes: {
      attr1: "value1",
      attr2: 42,
      attr3: true,
    },
  },
  () => {
    // Do something
  },
);

您也可以向已存在的跨度添加属性:

Copied
const span = Sentry.getActiveSpan();
if (span) {
  span.setAttribute("attr1", "value1");
  // Or set multiple attributes at once:
  span.setAttributes({
    attr2: 42,
    attr3: true,
  });
}

跨度可以关联一个操作,这有助于激活 Sentry 并提供关于该跨度的额外上下文信息。例如,与数据库相关的跨度通常会关联 db 操作。Sentry 产品为具有已知操作的跨度提供了额外的控制、可视化和过滤功能。

Sentry 维护了一个 常见的跨度操作列表,建议您在适用时使用这些操作之一。

Copied
const result = Sentry.startSpan({ name: 'GET /users', op: 'http.client' }, () => {
  return fetchUsers();
})

您可以在任何时候更新跨度的名称:

Copied
const span = Sentry.getActiveSpan();
if (span) {
  span.updateName("New Name");
}

请注意,在某些情况下,SDK 会覆盖跨度名称。以下是会自动设置名称的属性组合:

  • 具有 http.methodhttp.request.method 属性的跨度将自动将其名称设置为方法 + URL 路径
  • 具有 db.system 属性的跨度将自动将其名称设置为系统 + 语句