OpenTelemetry
可観測性は、Next.jsアプリの動作とパフォーマンスを理解し最適化するために重要です。
アプリケーションが複雑になるにつれて、発生する可能性のある問題を特定し診断することがますます困難になります。ログやメトリクスなどの可観測性ツールを駆使することで、開発者はアプリケーションの動作を把握し、最適化が必要な領域を特定できます。可観測性を活用することで、開発者は問題が重大化する前に積極的に対応し、より良いユーザーエクスペリエンスを提供できます。したがって、パフォーマンスを向上し、リソースを最適化し、ユーザーエクスペリエンスを向上させるため、Next.jsアプリケーションで可観測性を利用することを強くお勧めします。
アプリの計測にはOpenTelemetryを使用することをお勧めします。これは、プラットフォームに依存しない方法でアプリを計測し、コードを変更せずに可観測性プロバイダーを変更することができます。OpenTelemetryとその動作について詳しくは、公式OpenTelemetryドキュメントを参照してください。
このドキュメントでは、Span、Trace、Exporter などの用語を使用します。これらはすべて OpenTelemetry Observability Primer に記載されています。
Next.jsは、OpenTelemetryの計測を標準でサポートしています。つまり、Next.js自体がすでに計測されています。OpenTelemetryを有効化すると、getStaticPropsのようなすべてのコードが、役立つ属性を持つspansで自動的にラップされます。
はじめに
OpenTelemetryは拡張可能ですが、適切に設定するには非常に冗長になることがあります。そこで、すぐに始められるように @vercel/otel パッケージを用意しました。
@vercel/otelの使用
始めるには、次のパッケージをインストールします:
npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
次に、instrumentation.ts(または .js)ファイルをプロジェクトのルートディレクトリ(または src フォルダ内)に作成します:
- TypeScript
- JavaScript
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 ドキュメントを参照してください。
Good to know:
instrumentationファイルは、プロジェクトのルートに配置し、appまたはpagesディレクトリの中に配置しないようにしてください。srcフォルダを使用している場合は、pagesとappと並べて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 はedge runtimeと互換性がないため、process.env.NEXT_RUNTIME === 'nodejs' のときのみインポートされるようにする必要があります。これには instrumentation.node.ts という新しいファイルを作成し、nodeを使用しているときにのみ条件付きでインポートすることをお勧めします。
- TypeScript
- JavaScript
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')
}
}
- TypeScript
- JavaScript
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 に公開されていない機能を変更および拡張することが可能です。edge runtimeのサポートが必要な場合は、@vercel/otelを使用する必要があります。
計測をテストする
OpenTelemetryトレースをローカルでテストするには、互換性のあるバックエンドを持つOpenTelemetryコレクターが必要です。私たちの OpenTelemetry dev environmentを使用することをお勧めします。
すべてが正常に動作していれば、GET /requested/pathname とラベル付けされたルートサーバースパンが表示されるはずです。その特定のトレースからの他のすべてのスパンは、その下にネストされます。
Next.jsはデフォルトで発行されるより多くのスパンをトレースします。より多くのスパンを見るには、NEXT_OTEL_VERBOSE=1 を設定する必要があります。
デプロイ
OpenTelemetry Collectorの使用
OpenTelemetry Collectorを使用してデプロイする場合、@vercel/otel を使用できます。Vercelおよびセルフホスティングで動作します。
Vercelでのデプロイ
OpenTelemetryがVercelでそのまま動作することを確認しました。
プロジェクトを可観測性プロバイダーに接続するには、Vercelドキュメントに従ってください。
セルフホスティング
その他のプラットフォームへのデプロイも簡単です。Next.jsアプリからのテレメトリーデータを受信および処理するために、独自のOpenTelemetry Collectorを立ち上げる必要があります。
これを行うには、OpenTelemetry Collector Getting Startedガイドに従い、Collectorを設定し、Next.jsアプリからデータを受信するように構成してください。
Collectorを稼働させたら、それぞれのデプロイメントガイドに従って、選択したプラットフォームにNext.jsアプリをデプロイできます。
カスタムエクスポーター
OpenTelemetry Collectorは必須ではありません。カスタムOpenTelemetryエクスポーターを@vercel/otelや手動OpenTelemetry設定で使用できます。
カスタムスパン
カスタムスパンをOpenTelemetry APIsで追加できます。
npm install @opentelemetry/api
次の例は、GitHubスターをフェッチし、フェッチリクエストの結果を追跡するカスタム 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.rsc(true/false) - リクエストがプリフェッチのようなRSCリクエストであるかどうか。next.page- これはapp routerで使用される内部値です。
page.ts、layout.ts、loading.tsなどの特別なファイルへのルートとして考えることができます。/layoutは、/(groupA)/layout.tsと/(groupB)/layout.tsの両方を識別するためのユニークな識別子として使用できます。
[http.method] [next.route]
next.span_type:BaseServer.handleRequest
このスパンは、Next.jsアプリケーションへの各着信リクエストのルートスパンを表します。リクエストのHTTPメソッド、ルート、ターゲット、およびステータスコードを追跡します。
属性:
- 共通HTTP属性
http.methodhttp.status_code
- サーバーHTTP属性
http.routehttp.target
next.span_namenext.span_typenext.route
render route (app) [next.route]
next.span_type:AppRender.getBodyResult.
このスパンは、app routerでルートをレンダリングするプロセスを表します。
属性:
next.span_namenext.span_typenext.route
fetch [http.method] [http.url]
next.span_type:AppRender.fetch
このスパンは、コード内で実行されるフェッチリクエストを表します。
属性:
- 共通HTTP属性
http.method
- クライアントHTTP属性
http.urlnet.peer.namenet.peer.port(指定された場合のみ)
next.span_namenext.span_type
このスパンは、環境で NEXT_OTEL_FETCH_DISABLED=1 を設定することで無効にできます。これにより、カスタムフェッチ計測ライブラリを使用したい場合に便利です。
executing api route (app) [next.route]
next.span_type:AppRouteRouteHandlers.runHandler.
このスパンは、app routerでAPI Route Handlerを実行することを表します。
属性:
next.span_namenext.span_typenext.route
getServerSideProps [next.route]
next.span_type:Render.getServerSideProps.
このスパンは、特定のルートに対して getServerSideProps を実行することを表します。
属性:
next.span_namenext.span_typenext.route
getStaticProps [next.route]
next.span_type:Render.getStaticProps.
このスパンは、特定のルートに対して getStaticProps を実行することを表します。
属性:
next.span_namenext.span_typenext.route
render route (pages) [next.route]
next.span_type:Render.renderDocument.
このスパンは、特定のルートに対してドキュメントをレンダリングするプロセスを表します。
属性:
next.span_namenext.span_typenext.route
generateMetadata [next.page]
next.span_type:ResolveMetadata.generateMetadata.
このスパンは、特定のページに対してメタデータを生成するプロセスを表します(単一のルートに対して複数のスパンが存在する場合があります)。
属性:
next.span_namenext.span_typenext.page
resolve page components
next.span_type:NextNodeServer.findPageComponents.
このスパンは、特定のページのページコンポーネントを解決するプロセスを表します。
属性:
next.span_namenext.span_typenext.route
resolve segment modules
next.span_type:NextNodeServer.getLayoutOrPageModule.
このスパンは、レイアウトまたはページのコードモジュールのロードを表します。
属性:
next.span_namenext.span_typenext.segment
start response
next.span_type:NextNodeServer.startResponse.
このゼロ長スパンは、レスポンスで最初のバイトが送信されたときの時間を表します。