Menu

How to instrument your Next.js app with OpenTelemetry

可观测性对于理解和优化 Next.js 应用的行为和性能至关重要。

随着应用变得越来越复杂,识别和诊断可能出现的问题变得越来越困难。通过利用可观测性工具(如日志和指标),开发者可以深入了解应用的行为并识别需要优化的区域。有了可观测性,开发者可以在问题变成重大问题之前主动解决它们,并提供更好的用户体验。因此,强烈建议在你的 Next.js 应用中使用可观测性来提升性能、优化资源并增强用户体验。

我们推荐使用 OpenTelemetry 来对你的应用进行 instrumentation。 这是一种与平台无关的 instrumentation 方式,允许你在不更改代码的情况下更换可观测性提供商。 阅读 OpenTelemetry 官方文档以获取更多关于 OpenTelemetry 及其工作原理的信息。

本文档在整个文档中使用了 SpanTraceExporter 等术语,所有这些术语都可以在 OpenTelemetry 可观测性入门中找到。

Next.js 开箱即支持 OpenTelemetry instrumentation,这意味着我们已经对 Next.js 本身进行了 instrumentation。

当你启用 OpenTelemetry 时,我们将自动用带有有用属性的 spans 包装你的所有代码,如 getStaticProps

开始使用

OpenTelemetry 是可扩展的,但正确设置它可能会相当冗长。 这就是为什么我们准备了一个 @vercel/otel 包来帮助你快速开始。

使用 @vercel/otel

要开始使用,请安装以下包:

Terminal
npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation

接下来,在项目的根目录(如果使用 src 文件夹则在其内部)创建一个自定义的 instrumentation.ts(或 .js)文件:

your-project/instrumentation.ts
TypeScript
import { registerOTel } from '@vercel/otel'
 
export function register() {
  registerOTel({ serviceName: 'next-app' })
}

查看 @vercel/otel 文档以了解其他配置选项。

值得注意的是

  • instrumentation 文件应该位于项目的根目录,而不是 apppages 目录内。如果你使用 src 文件夹,则将文件放在 src 内部,与 pagesapp 并列。
  • 如果你使用 pageExtensions 配置选项来添加后缀,你还需要更新 instrumentation 文件名以匹配。
  • 我们创建了一个基本的 with-opentelemetry 示例供你使用。

手动配置 OpenTelemetry

@vercel/otel 包提供了许多配置选项,应该能满足大多数常见用例。但如果它不符合你的需求,你可以手动配置 OpenTelemetry。

首先,你需要安装 OpenTelemetry 包:

Terminal
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http

现在你可以在 instrumentation.ts 中初始化 NodeSDK。 与 @vercel/otel 不同,NodeSDK 与 edge runtime 不兼容,因此你需要确保只在 process.env.NEXT_RUNTIME === 'nodejs' 时导入它们。我们建议创建一个新文件 instrumentation.node.ts,只在使用 node 时有条件地导入它:

instrumentation.ts
TypeScript
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}
instrumentation.node.ts
TypeScript
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
 
const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()

这样做相当于使用 @vercel/otel,但可以修改和扩展一些 @vercel/otel 未公开的功能。如果需要支持 edge runtime,你必须使用 @vercel/otel

测试你的 instrumentation

你需要一个带有兼容后端的 OpenTelemetry collector 来在本地测试 OpenTelemetry traces。 我们推荐使用我们的 OpenTelemetry 开发环境

如果一切正常,你应该能够看到标记为 GET /requested/pathname 的根服务器 span。 该特定 trace 的所有其他 spans 都将嵌套在其下。

Next.js 追踪的 spans 比默认发出的要多。 要查看更多 spans,你必须设置 NEXT_OTEL_VERBOSE=1

部署

使用 OpenTelemetry Collector

当你使用 OpenTelemetry Collector 进行部署时,可以使用 @vercel/otel。 它在 Vercel 和自托管时都可以工作。

在 Vercel 上部署

我们确保 OpenTelemetry 在 Vercel 上开箱即用。

遵循 Vercel 文档将你的项目连接到可观测性提供商。

自托管

部署到其他平台也很简单。你需要启动自己的 OpenTelemetry Collector 来接收和处理来自 Next.js 应用的遥测数据。

为此,请遵循 OpenTelemetry Collector 入门指南,它将引导你完成 collector 的设置,并将其配置为接收来自 Next.js 应用的数据。

一旦你的 collector 启动并运行,你可以按照各自的部署指南将 Next.js 应用部署到你选择的平台。

自定义 Exporters

不一定需要 OpenTelemetry Collector。你可以将自定义 OpenTelemetry exporter 与 @vercel/otel手动 OpenTelemetry 配置一起使用。

自定义 Spans

你可以使用 OpenTelemetry APIs 添加自定义 span。

Terminal
npm install @opentelemetry/api

以下示例演示了一个获取 GitHub stars 的函数,并添加了一个自定义 fetchGithubStars span 来追踪 fetch 请求的结果:

import { trace } from '@opentelemetry/api'
 
export async function fetchGithubStars() {
  return await trace
    .getTracer('nextjs-example')
    .startActiveSpan('fetchGithubStars', async (span) => {
      try {
        return await getValue()
      } finally {
        span.end()
      }
    })
}

register 函数将在你的代码在新环境中运行之前执行。 你可以开始创建新的 spans,它们应该被正确地添加到导出的 trace 中。

Next.js 中的默认 Spans

Next.js 自动为你 instrument 了多个 spans,以提供对应用性能的有用洞察。

spans 上的属性遵循 OpenTelemetry 语义约定。我们还在 next 命名空间下添加了一些自定义属性:

  • next.span_name - 复制 span 名称
  • next.span_type - 每个 span 类型都有一个唯一标识符
  • next.route - 请求的路由模式(例如,/[param]/user)。
  • next.rsc(true/false)- 请求是否是 RSC 请求,例如 prefetch。
  • next.page
    • 这是 app router 使用的内部值。
    • 你可以将其视为特殊文件的路由(如 page.tslayout.tsloading.ts 等)
    • 它只能在与 next.route 配对时用作唯一标识符,因为 /layout 可用于识别 /(groupA)/layout.ts/(groupB)/layout.ts

[http.method] [next.route]

  • next.span_typeBaseServer.handleRequest

此 span 表示每个传入 Next.js 应用请求的根 span。它追踪请求的 HTTP 方法、路由、目标和状态码。

属性:

render route (app) [next.route]

  • next.span_typeAppRender.getBodyResult

此 span 表示在 app router 中渲染路由的过程。

属性:

  • next.span_name
  • next.span_type
  • next.route

fetch [http.method] [http.url]

  • next.span_typeAppRender.fetch

此 span 表示在你的代码中执行的 fetch 请求。

属性:

可以通过在环境中设置 NEXT_OTEL_FETCH_DISABLED=1 来关闭此 span。当你想使用自定义 fetch instrumentation 库时,这很有用。

executing api route (app) [next.route]

  • next.span_typeAppRouteRouteHandlers.runHandler

此 span 表示在 app router 中执行 API Route Handler。

属性:

  • next.span_name
  • next.span_type
  • next.route

getServerSideProps [next.route]

  • next.span_typeRender.getServerSideProps

此 span 表示特定路由的 getServerSideProps 的执行。

属性:

  • next.span_name
  • next.span_type
  • next.route

getStaticProps [next.route]

  • next.span_typeRender.getStaticProps

此 span 表示特定路由的 getStaticProps 的执行。

属性:

  • next.span_name
  • next.span_type
  • next.route

render route (pages) [next.route]

  • next.span_typeRender.renderDocument

此 span 表示为特定路由渲染文档的过程。

属性:

  • next.span_name
  • next.span_type
  • next.route

generateMetadata [next.page]

  • next.span_typeResolveMetadata.generateMetadata

此 span 表示为特定页面生成 metadata 的过程(单个路由可以有多个这样的 spans)。

属性:

  • next.span_name
  • next.span_type
  • next.page

resolve page components

  • next.span_typeNextNodeServer.findPageComponents

此 span 表示为特定页面解析页面组件的过程。

属性:

  • next.span_name
  • next.span_type
  • next.route

resolve segment modules

  • next.span_typeNextNodeServer.getLayoutOrPageModule

此 span 表示为 layout 或 page 加载代码模块。

属性:

  • next.span_name
  • next.span_type
  • next.segment

start response

  • next.span_typeNextNodeServer.startResponse

此零长度 span 表示在响应中发送第一个字节的时间。