如何使用 OpenTelemetry 设置监控

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

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

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

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

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

快速开始

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' })
}
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')
  }
}
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.js')
  }
}
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } 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: new Resource({
    [ATTR_SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } 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: new Resource({
    [ATTR_SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()

此配置等效于使用 @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 表示响应中发送第一个字节的时间点。