如何使用 OpenTelemetry 监控你的 Next.js 应用

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

随着应用日益复杂,识别和诊断潜在问题变得愈发困难。通过利用日志记录和指标等可观测性工具,开发者可以洞察应用行为并找到优化点。借助可观测性,开发者能主动解决问题,避免其演变为重大故障,从而提供更佳用户体验。因此,强烈建议在 Next.js 应用中使用可观测性来提升性能、优化资源并增强用户体验。

我们推荐使用 OpenTelemetry 来实现应用监控。
这是一个与平台无关的监控方案,让你无需修改代码即可更换可观测性供应商。
更多关于 OpenTelemetry 及其工作原理的信息,请阅读 OpenTelemetry 官方文档

本文档中会反复出现 Span(跨度)、Trace(追踪)或 Exporter(导出器)等术语,这些概念均可在 OpenTelemetry 可观测性入门 中找到。

Next.js 已内置支持 OpenTelemetry 监控,这意味着我们已为 Next.js 本身实现了监控。

启用 OpenTelemetry 后,我们会自动用带有有用属性的 spans 包裹所有代码,如 getStaticProps

快速开始

OpenTelemetry 具有高度可扩展性,但正确配置可能较为繁琐。
为此我们准备了 @vercel/otel 包来帮助你快速上手。

使用 @vercel/otel

首先安装以下包:

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

接着在项目根目录(或使用 src 文件夹时在其中)创建自定义 instrumentation.ts(或 .js)文件:

import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel({ serviceName: 'next-app' })
}

更多配置选项请参阅 @vercel/otel 文档

须知

  • instrumentation 文件应位于项目根目录,而非 apppages 目录内。若使用 src 文件夹,请将文件放在与 pagesapp 同级的 src 中。
  • 如果使用 pageExtensions 配置选项 添加后缀,也需相应调整 instrumentation 文件名。
  • 我们提供了一个基础示例 with-opentelemetry 供参考。

手动配置 OpenTelemetry

@vercel/otel 包提供了丰富的配置选项,能满足大多数常见需求。但如果无法满足你的需求,可以手动配置 OpenTelemetry。

首先安装 OpenTelemetry 包:

终端
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 不兼容边缘运行时,因此需确保仅在 process.env.NEXT_RUNTIME === 'nodejs' 时导入。建议新建 instrumentation.node.ts 文件,并仅在 node 环境下条件导入:

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}

此配置等效于使用 @vercel/otel,但可修改和扩展 @vercel/otel 未暴露的功能。如需边缘运行时支持,则必须使用 @vercel/otel

测试监控配置

本地测试 OpenTelemetry 追踪需要搭配 OpenTelemetry 收集器与兼容的后端。
推荐使用我们的 OpenTelemetry 开发环境

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

Next.js 会追踪比默认输出更多的 spans。
要查看更多 spans,需设置 NEXT_OTEL_VERBOSE=1

部署

使用 OpenTelemetry 收集器

当使用 OpenTelemetry 收集器部署时,可以选用 @vercel/otel
该方案在 Vercel 和自托管环境中均可工作。

部署到 Vercel

我们已确保 OpenTelemetry 在 Vercel 上开箱即用。
按照 Vercel 文档 将项目连接到可观测性供应商。

自托管

部署到其他平台也很简单。你需要自行启动 OpenTelemetry 收集器来接收和处理来自 Next.js 应用的遥测数据。
具体操作请遵循 OpenTelemetry 收集器入门指南,该指南将引导你完成收集器设置和配置以接收 Next.js 应用数据。
收集器运行后,即可按照各平台的部署指南部署 Next.js 应用。

自定义导出器

并非必须使用 OpenTelemetry 收集器。你可以通过 @vercel/otel手动 OpenTelemetry 配置 使用自定义 OpenTelemetry 导出器。

自定义 Spans

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

终端
npm install @opentelemetry/api

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

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,它们将被正确添加到导出的追踪中。

Next.js 中的默认 Spans

Next.js 自动监控多个 spans,为应用性能提供有价值的洞察。

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

  • next.span_name - 重复 span 名称
  • next.span_type - 每个 span 类型有唯一标识符
  • next.route - 请求的路由模式(如 /[param]/user
  • next.rsc (true/false) - 请求是否为 RSC 请求(如预取)
  • next.page
    • 这是应用路由使用的内部值
    • 可视为指向特殊文件的路由(如 page.tslayout.tsloading.ts 等)
    • 只有与 next.route 结合时才能作为唯一标识符,因为 /layout 可能同时标识 /(groupA)/layout.ts/(groupB)/layout.ts

[http.method] [next.route]

  • next.span_type: BaseServer.handleRequest

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

属性:

渲染路由 (app) [next.route]

  • next.span_type: AppRender.getBodyResult

此 span 表示在应用路由中渲染路由的过程。

属性:

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

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch

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

属性:

可通过设置环境变量 NEXT_OTEL_FETCH_DISABLED=1 关闭此 span。这在需要使用自定义 fetch 监控库时很有用。

执行 API 路由 (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler

此 span 表示应用路由中 API 路由处理程序的执行过程。

属性:

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

getServerSideProps [next.route]

  • next.span_type: Render.getServerSideProps

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

属性:

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

getStaticProps [next.route]

  • next.span_type: Render.getStaticProps

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

属性:

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

渲染路由 (pages) [next.route]

  • next.span_type: Render.renderDocument

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

属性:

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

generateMetadata [next.page]

  • next.span_type: ResolveMetadata.generateMetadata

此 span 表示为特定页面生成元数据的过程(单个路由可能有多个此类 spans)。

属性:

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

解析页面组件

  • next.span_type: NextNodeServer.findPageComponents

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

属性:

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

解析模块片段

  • next.span_type: NextNodeServer.getLayoutOrPageModule

此 span 表示加载布局或页面的代码模块。

属性:

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

开始响应

  • next.span_type: NextNodeServer.startResponse

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