shwld.io

NestJSのGraphQLサーバにApplication Insightsのトレースを仕込む


NestJS (GraphQL) アプリをAzureのAppServiceでホストしているのですが、どうもレスポンスが遅いということで、調査のためにトレースログを仕込む方法を調べたログです。

Apollo Serverのプラグインを作成し、Apolloの各種イベントにカスタムトレースログを出力するコードを書きます。

import * as appInsights from 'applicationinsights';
import { ApolloServerPlugin, GraphQLRequestListener } from '@apollo/server';
import { Plugin } from '@nestjs/apollo';

/**
 * GraphQLの各種イベント実行時にAzureのApplicationInsightsへトレースを送信するためのプラグイン
 */
@Plugin()
export class ApplicationInsightsApolloServerPlugin
  implements ApolloServerPlugin
{
  async requestDidStart(): Promise<GraphQLRequestListener<any>> {
    const client = appInsights.defaultClient;
    client.trackMetric({ name: 'graphql-query', value: 1 });
    return {
      // GraphQLのASTを解析後実行が開始したら呼ばれます
      // REF: https://www.apollographql.com/docs/apollo-server/integrations/plugins-event-reference/#executiondidstart
      async executionDidStart(requestContext) {
        if (requestContext.errors) {
          client.trackException({
            exception: new Error(
              `graphql-error: ${JSON.stringify(requestContext.errors)}`,
            ),
          });
        } else {
          client.trackTrace({
            message: `graphql executionDidStart: ${JSON.stringify({
              operationName: requestContext.request.operationName,
              query: requestContext.request.query,
              variables: requestContext.request.variables,
              request: requestContext.request,
            })}`,
          });
        }
      },
      // GraphQLのレスポンス送信のたびに呼ばれます
      // REF: https://www.apollographql.com/docs/apollo-server/integrations/plugins-event-reference/#willsendresponse
      async willSendResponse(requestContext) {
        if (requestContext.errors) {
          client.trackException({
            exception: new Error(
              `graphql-error: ${JSON.stringify(requestContext.errors)}`,
            ),
          });
        } else {
          client.trackTrace({
            message: `graphql willSendResponse: ${JSON.stringify(
              requestContext.response,
            )}`,
          });
        }
      },
    };
  }
}

client.trackTrace によってトレースされたデータは、Application Insights -> Logs の traces テーブルへ格納されます。

Apolloのイベントはたくさんあるので、トレースしたい箇所のイベントを探して仕込みましょう。

https://www.apollographql.com/docs/apollo-server/integrations/plugins-event-reference/

AppModuleなどでPluginを読み込む必要があります。

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { ApplicationInsightsApolloServerPlugin } from './application-insights-apollo-server-plugin';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      ...
    }),
  ],
  providers: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING
    ? [ApplicationInsightsApolloServerPlugin]
    : [],
})
export class AppModule {}

利用するために、main.tsの速い段階で、applicationInsightsをstart()しておく必要があります。

import { NestFactory } from '@nestjs/core';
import * as appInsights from 'applicationinsights';

import { AppModule } from './app.module';

async function bootstrap() {
  if (process.env.APPLICATIONINSIGHTS_CONNECTION_STRING) {
    appInsights
      .setup(process.env.APPLICATIONINSIGHTS_CONNECTION_STRING)
      .start();
  }

  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

参考:

NestJSのGraphQLサーバにApplication Insightsのトレースを仕込む
shwld
2024/02/19
GitHub Copilotを効率よく使うために
shwld
2024/01/29
2024年。今年の抱負
shwld
2024/01/18
DevOps活動の半年間の振り返り:2つのプロジェクトの経験と教訓
shwld
2023/12/07