自定义插桩
了解如何捕获应用程序中任何操作的性能数据。
要捕获符合您组织需求的自定义事务和跨度,您必须首先 设置追踪。
要为您的应用程序添加自定义性能数据,您需要以跨度的形式添加自定义插桩。跨度是一种测量特定操作所需时间的方法。例如,您可以创建一个跨度来测量函数执行所需的时间。
要开始,请导入 SDK。
import * as Sentry from "@sentry/browser";
创建跨度有三个关键函数:
- startSpan:创建一个新的活动跨度,并自动结束。您可能希望使用此函数。
- startSpanManual:创建一个新的活动跨度,但需要手动结束。
- startInactiveSpan:创建一个新的非活动跨度,需要手动结束。
当启动一个新的跨度时,如果当前有活动的父跨度,它将自动作为该父跨度的子跨度启动。这意味着,如果一个跨度被启动为 活动跨度,在该跨度活跃期间创建的任何其他跨度将成为它的子跨度。此外,错误将与当前活动的跨度关联(如果有)。
相比之下,非活动跨度 不会自动关联任何子跨度。这在您不关心捕获子活动的情况下非常有用。
活动跨度的一个关键约束是它们只能在回调函数中激活。这个约束存在的原因是,在处理异步代码时,否则无法将跨度正确地关联到父跨度。
在无法将执行代码包装在回调函数中的地方(例如,使用钩子或其他类似情况),您需要使用非活动跨度,并可以结合 withActiveSpan 手动将子跨度与正确的父跨度关联。
在浏览器环境中,默认情况下,跨度以扁平层次结构收集,每个跨度都是根跨度的直接子跨度。您可以选择更细粒度的层次结构,但这会带来一些权衡。
保持扁平层次结构的主要原因是,在浏览器中无法可靠地跨异步边界跟踪活动跨度。这意味着,如果多个异步操作并行启动,无法确定哪个跨度是哪个子跨度的父跨度。想象以下示例:
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
的子跨度。这会导致误导和混淆,因此,默认情况下,在浏览器中,所有跨度都会成为根跨度的子跨度(通常是 pageload
或 navigation
跨度)。这意味着您将始终拥有一个扁平的跨度层次结构。
这是我们在确保捕获的数据准确和可靠的前提下所做的权衡。如果您需要捕获更复杂的跨度层次结构,可以通过设置 parentSpanIsAlwaysRootSpan: false
来禁用此行为:
Sentry.init({
parentSpanIsAlwaysRootSpan: false,
});
这将恢复为使用完整的层次结构行为,其中跨度是当前活动跨度的子跨度。但是,在多个并行异步操作的情况下,这可能导致数据不正确——在这种情况下,确保没有多个并行异步操作启动跨度的责任在于您。
以下选项可以用于所有启动跨度的函数:
选项 | 类型 | 描述 |
---|---|---|
name | string | 跨度的名称。 |
op | string | 跨度的操作类型。 |
startTime | number | 跨度的开始时间。 |
attributes | Record<string, Primitive> | 要附加到跨度的属性。 |
parentSpan | Span | 如果设置,将使该跨度成为指定跨度的子跨度。否则,该跨度将成为当前活动跨度的子跨度。 |
onlyIfParent | boolean | 如果为 true ,在没有活动父跨度时忽略该跨度。 |
forceTransaction | boolean | 如果为 true ,确保此跨度在 Sentry UI 中显示为事务。 |
只有 name
是必需的,其他所有选项都是可选的。
对于大多数场景,我们建议使用 Sentry.startSpan()
启动活动跨度。这将启动一个新的活动跨度,在提供的回调函数中激活,并在回调函数完成后自动结束该跨度。回调函数可以是同步的,也可以是异步的(返回一个 Promise)。
为同步操作启动一个跨度:
const result = Sentry.startSpan({ name: "Important Function" }, () => {
return expensiveFunction();
});
为异步操作启动一个跨度:
const result = await Sentry.startSpan(
{ name: "Important Function" },
async () => {
const res = await doSomethingAsync();
return updateRes(res);
},
);
您还可以嵌套跨度:
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()
手动结束该跨度。
// 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。
const span1 = Sentry.startInactiveSpan({ name: "span1" });
someWork();
span1.end();
默认情况下,启动的任何跨度都会成为当前活动跨度的子跨度。如果您希望有不同的行为,可以使用 parentSpan
选项强制跨度成为特定跨度的子跨度:
const parentSpan = Sentry.startInactiveSpan({ name: "Parent Span" });
const childSpan = Sentry.startInactiveSpan({ name: "Child Span", parentSpan });
childSpan.end();
parentSpan.end();
此选项也适用于 startSpan
和 startSpanManual
。
我们提供了一些有用的工具,可以帮助您进行自定义插桩。
返回当前活动的跨度。
const activeSpan = Sentry.getActiveSpan();
返回给定跨度的根跨度。如果该跨度已经是根跨度,则返回该跨度本身。
const activeSpan = Sentry.getActiveSpan();
const rootSpan = activeSpan ? Sentry.getRootSpan(activeSpan) : undefined;
此方法允许您在回调函数的执行期间使一个跨度成为活动状态。您可以将其与 startInactiveSpan
结合使用,以手动将子跨度与正确的父跨度关联:
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
,以确保该跨度没有父跨度:
Sentry.withActiveSpan(null, () => {
// This will not have a parent span, no matter what
Sentry.startSpan({ name: "Parent Span" }, () => {
// Do something
});
});
或者,您也可以使用 parentSpan
选项来达到相同的效果:
const span = Sentry.startInactiveSpan({ name: "Parent Span" });
const childSpan = Sentry.startInactiveSpan({
name: "Child Span",
parentSpan: span,
});
在回调函数执行期间抑制采样跨度的创建。这在您希望阻止某些跨度被捕获时非常有用。例如,如果您不希望为某个 fetch
请求创建跨度,可以这样做:
Sentry.suppressTracing(() => {
fetch("https://example.com");
});
有关如何手动设置分布式追踪的详细信息,请参阅 分布式追踪。
您可以在创建跨度时捕获跨度属性。跨度属性可以是 string
、number
或 boolean
类型,也可以是这些类型的(非混合)数组。您可以在启动跨度时指定属性:
Sentry.startSpan(
{
attributes: {
attr1: "value1",
attr2: 42,
attr3: true,
},
},
() => {
// Do something
},
);
或者您也可以向现有的跨度添加属性:
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 维护了一个 常见跨度操作列表,建议您在适用时使用这些操作之一。
const result = Sentry.startSpan({ name: 'GET /users', op: 'http.client' }, () => {
return fetchUsers();
})
自 v8.47.0 可用
您可以在任何时候更新跨度的名称:
const span = Sentry.getActiveSpan();
if (span) {
Sentry.updateSpanName(span, "New Name");
}
在 v8.39.0 之前,您必须使用 span.updateName('New Name')
,这在 @sentry/node
及依赖它的 SDK(例如 @sentry/nextjs
)中有一些限制:
- 具有
http.method
或http.request.method
属性的跨度会自动将其名称设置为方法 + URL 路径。 - 具有
db.system
属性的跨度会自动将其名称设置为系统 + 语句。
使用 Sentry.updateSpanName()
可确保名称正确更新,并且在这些情况下不再被覆盖。
如果您在浏览器环境中使用 @sentry/browser
、@sentry/react
等,span.updateName()
和 Sentry.updateSpanName()
的功能是相同的,因此您可以使用其中任何一个。