OpenTelemetry

知っておくと良いこと: この機能は実験的であり、next.config.jsexperimental.instrumentationHook = true;を明示的に設定する必要があります。

オブザーバビリティ(可観測性)は、Next.jsアプリケーションの動作とパフォーマンスを理解し最適化するために重要です。

アプリケーションが複雑になるにつれ、発生する可能性のある問題を特定・診断することが難しくなります。ロギングやメトリクスなどのオブザーバビリティツールを活用することで、開発者はアプリケーションの動作を把握し、最適化すべき領域を特定できます。オブザーバビリティにより、開発者は問題が重大化する前に先手を打って対処し、より良いユーザー体験を提供できます。したがって、Next.jsアプリケーションでオブザーバビリティを使用してパフォーマンスを向上させ、リソースを最適化し、ユーザー体験を向上させることを強く推奨します。

アプリの計装にはOpenTelemetryを使用することをお勧めします。 これはプラットフォームに依存しない計装方法で、コードを変更せずにオブザーバビリティプロバイダーを変更できます。 OpenTelemetryの詳細と動作原理については公式OpenTelemetryドキュメントを参照してください。

このドキュメントでは_Span_、Trace、_Exporter_などの用語を使用します。これらはすべてOpenTelemetry Observability Primerで説明されています。

Next.jsはOpenTelemetry計装をデフォルトでサポートしており、Next.js自体が既に計装されています。OpenTelemetryを有効にすると、getStaticPropsなどのすべてのコードが有用な属性を持つ_span_で自動的にラップされます。

知っておくと良いこと: 現在、OpenTelemetryバインディングはサーバーレス関数でのみサポートされています。 edgeやクライアントサイドコードには提供していません。

はじめに

OpenTelemetryは拡張可能ですが、適切に設定するにはかなり詳細な記述が必要です。 そのため、迅速に開始できるように@vercel/otelパッケージを用意しました。 これは拡張可能ではなく、設定をカスタマイズする必要がある場合は手動でOpenTelemetryを構成する必要があります。

@vercel/otelの使用

まず、@vercel/otelをインストールする必要があります:

ターミナル
npm install @vercel/otel

次に、プロジェクトのルートディレクトリ(またはsrcフォルダを使用している場合はその中)にカスタムinstrumentation.ts(または.js)ファイルを作成します:

import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel('next-app')
}
import { registerOTel } from '@vercel/otel'

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

知っておくと良いこと

  • 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.tsNodeSDKを初期化します。 OpenTelemetry APIはedgeランタイムと互換性がないため、process.env.NEXT_RUNTIME === 'nodejs'の場合のみインポートする必要があります。nodeを使用する場合のみ条件付きでインポートする新しいファイルinstrumentation.node.tsを作成することをお勧めします:

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 { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()

これは@vercel/otelを使用するのと同等ですが、変更や拡張が可能です。 例えば、@opentelemetry/exporter-trace-otlp-httpの代わりに@opentelemetry/exporter-trace-otlp-grpcを使用したり、より多くのリソース属性を指定したりできます。

計装のテスト

OpenTelemetryトレースをローカルでテストするには、OpenTelemetryコレクターと互換性のあるバックエンドが必要です。 OpenTelemetry開発環境の使用をお勧めします。

すべてが正しく動作していれば、GET /requested/pathnameとラベル付けされたルートサーバースパンが表示されるはずです。 その特定のトレースからの他のすべてのスパンは、その下にネストされます。

Next.jsはデフォルトで出力されるよりも多くのスパンをトレースします。 より多くのスパンを表示するには、NEXT_OTEL_VERBOSE=1を設定する必要があります。

デプロイ

OpenTelemetryコレクターの使用

OpenTelemetryコレクターでデプロイする場合、@vercel/otelを使用できます。 これはVercelでもセルフホスティングでも動作します。

Vercelへのデプロイ

Vercel上でOpenTelemetryがすぐに動作するようにしました。

プロジェクトをオブザーバビリティプロバイダーに接続するにはVercelドキュメントに従ってください。

セルフホスティング

他のプラットフォームへのデプロイも簡単です。Next.jsアプリからテレメトリデータを受信して処理するために、独自のOpenTelemetryコレクターを起動する必要があります。

これを行うには、OpenTelemetryコレクター入門ガイドに従って、コレクターをセットアップし、Next.jsアプリからデータを受信するように構成します。

コレクターが起動して実行されたら、選択したプラットフォームにNext.jsアプリをデプロイできます。それぞれのデプロイガイドに従ってください。

カスタムエクスポーター

OpenTelemetryコレクターの使用をお勧めします。 プラットフォームでそれが不可能な場合は、手動OpenTelemetry設定でカスタムOpenTelemetryエクスポーターを使用できます。

カスタムスパン

OpenTelemetry APIを使用してカスタムスパンを追加できます。

ターミナル
npm install @opentelemetry/api

次の例は、GitHubのスター数を取得し、fetchリクエストの結果を追跡するためにfetchGithubStarsというカスタムスパンを追加する関数を示しています:

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関数は、新しい環境でコードが実行される前に実行されます。 新しいスパンを作成し始めると、それらは正しくエクスポートされたトレースに追加されるはずです。

Next.jsのデフォルトスパン

Next.jsは、アプリケーションのパフォーマンスに関する有用な洞察を提供するために、いくつかのスパンを自動的に計装します。

スパンの属性はOpenTelemetryセマンティック規約に従います。また、next名前空間の下にいくつかのカスタム属性を追加します:

  • next.span_name - スパン名を複製
  • next.span_type - 各スパンタイプに一意の識別子
  • next.route - リクエストのルートパターン(例:/[param]/user)。
  • next.page
    • これはappルーターで使用される内部値です。
    • page.tslayout.tsloading.tsなどの特別なファイルへのルートと考えることができます
    • next.routeと組み合わせた場合のみ一意の識別子として使用できます。なぜなら、/layout/(groupA)/layout.ts/(groupB)/layout.tsの両方を識別するために使用できるからです

[http.method] [next.route]

  • next.span_type: BaseServer.handleRequest

このスパンは、Next.jsアプリケーションへの各着信リクエストのルートスパンを表します。リクエストのHTTPメソッド、ルート、ターゲット、およびステータスコードを追跡します。

属性:

render route (app) [next.route]

  • next.span_type: AppRender.getBodyResult.

このスパンは、appルーターでのルートのレンダリングプロセスを表します。

属性:

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

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch.

このスパンは、コードで実行されたfetchリクエストを表します。

属性:

executing api route (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler.

このスパンは、appルーターでのAPIルートハンドラーの実行を表します。

属性:

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

getServerSideProps [next.route]

  • next.span_type: Render.getServerSideProps.

このスパンは、特定のルートに対するgetServerSidePropsの実行を表します。

属性:

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

getStaticProps [next.route]

  • next.span_type: Render.getStaticProps.

このスパンは、特定のルートに対するgetStaticPropsの実行を表します。

属性:

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

render route (pages) [next.route]

  • next.span_type: Render.renderDocument.

このスパンは、特定のルートに対するドキュメントのレンダリングプロセスを表します。

属性:

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

generateMetadata [next.page]

  • next.span_type: ResolveMetadata.generateMetadata.

このスパンは、特定のページのメタデータ生成プロセスを表します(1つのルートに複数のスパンが存在する場合があります)。

属性:

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