📈

Next.jsでApplication Insightsを使ってみる

に公開

はじめに

Azure において APM(アプリケーション監視)が行えるサービスとして Application Insights というサービスが提供されています。
Application Insights は様々な機能が提供されており何かと便利なのですが、イマイチ使い方が分かりにくいことがあります。特に Next.js のようなフロントエンドのフレームワークにおいては実行される場所によってライブラリを使い分ける必要があります。
今回は Next.js において Application Insights にテレメトリーを送信し、APM を開始する方法を共有します。

使用するライブラリ

現在、Application Insights へのテレメトリー送信において公式から提供されている JavaScript 用ライブラリとしては@microsoft/applicationinsights-web@azure/monitor-opentelemetryの 2 つです。
Application Insightsのライブラリ使い分け

各ライブラリの使い分けとしては@microsoft/applicationinsights-web はクライアントサイド、@azure/monitor-opentelemetry はサーバーサイドで使用するライブラリとなります。

Next.js においてサーバーサイドやクライアントサイドという言葉は混乱しやすいのですが、あくまで実行場所の話のため、サーバーコンポーネントのようなサーバーサイドで実行されるフロントエンドにおいても@azure/monitor-opentelemetry を使用する形になります。

サーバーサイド側の計測

前述の通り、@azure/monitor-opentelemetry を使用します。
このライブラリは Microsoft がサポートする Application Insights(Azure Monitor)用の OpenTelemetry のディストリビューションです。
OpenTelemetry の説明については世の中に出回っているので割愛します。
@azure/monitor-opentelemetryにおいてはuseAzureMonitor関数を実行することで OpenTelemetry を使用した自動計測を行うことができます。

useAzureMonitor関数は実行時に一回のみ実行されることが期待されており、ここで使用されるのが instrumentation.ts です。これは middleware.ts 同様 src ディレクトリ配下に置くことでサーバー起動時に一回のみ実行される処理を記述することができます。

https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation

公式ドキュメントには@vercel/otelの使用例が記載されていますが、同様に@azure/monitor-opentelemetry を使用することができます。

src/instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    console.log("start instrumain");
    await import("./instrumentation.node");
  }
}
src/instrumentation.node.ts
import { useAzureMonitor } from "@azure/monitor-opentelemetry";

// eslint-disable-next-line react-hooks/rules-of-hooks
useAzureMonitor({
  azureMonitorExporterOptions: {
    connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
  },
});

クライアント側の計測

@microsoft/applicationinsights-web を使用します。
サーバー側と異なり、クライアント側はブラウザ内で初期化されるため、セッション毎で初期化させることが期待されます。そのため、ルートページの layout.tsx 内で初期化する等で対応することで自動計測を開始させることができます。

また、手動計測においてはサーバー側と異なり api 経由での取得ができないため、シングルトンクラスのインスタンスを使用して行うことになります。
そのため、以下のようにインスタンスを取得する関数を定義することで二重で初期化されることを防ぎながら手動収集を行うことができます。

src/utils/app-insights.ts
import { ReactPlugin } from "@microsoft/applicationinsights-react-js";
import { ApplicationInsights } from "@microsoft/applicationinsights-web";

let appInsights: ApplicationInsights | null = null;

export function getAppInsights() {
  if (appInsights === null) {
    const reactPlugin = new ReactPlugin();
    appInsights = new ApplicationInsights({
      config: {
        connectionString:
          process.env.NEXT_PUBLIC_APPLICATION_INSIGHTS_CONNECTION_STRING,
        enableAutoRouteTracking: true,
        extensionConfig: [reactPlugin],
      },
    });
    console.log("Start load Application Insights");
    appInsights.loadAppInsights();
  }
  return appInsights;
}

サンプルアプリによる計測例

さて、以上の説明を含めてサンプルアプリケーションを実装してみます。
コードは以下に格納してあります。

https://github.com/rom6621/nextjs-insights-sample

サーバーサイドの手動計測については Node.js の OpenTelemetry SDK と同様に@opentelemetry/apiを使用することで行うことができます。
以下は Server Actions でトレーシングを行う例です。

src/server/getTodo.ts
"use server"

import { trace } from '@opentelemetry/api';

export async function getTodos() {
	const tracer = trace.getTracer("todo tracer");
	...
  getSpan.addEvent("5秒待ちます");
  await new Promise((resolve) => setTimeout(resolve, 5000));
  getSpan.addEvent("処理を完了しました");
  getSpan.end();

	const getSpan.end();
	...
}

すると、Application Insights に以下のように送信されます。
Server Actionsのログ
Server Actionsのトレース
きちんとトレーシングが行われることがわかります。

次に、コンポーネント内でのクリックをカスタムイベントとして収集してみます。

src/components/todo-list.tsx
"use client";

import { getAppInsights } from "@/utils/app-insights";

import type { Todo } from "@/types/todo";

type Props = {
  todos: Todo[];
};

export const TodoList = ({ todos }: Props) => {
  const onClickTodo = (todo: Todo) => {
    const appInsights = getAppInsights();
    appInsights.trackEvent({
      name: `${todo.name}がクリックされました`,
    });
  };

  return (
    <ul>
      {todos.map((todo) => {
        <li key={todo.id} onClick={() => onClickTodo(todo)}>
          {todo.title}
        </li>;
      })}
    </ul>
  );
};

すると以下のように Application Insights に送信されます。
クライアントコンポーネントのログ
これにより Application Insights で RUM(リアルタイムユーザーモニタリング)を行うことができます。

GitHubで編集を提案

Discussion