NestJS で Prisma の発行するクエリを出力する


NestJS で Prisma を扱うとき、発行されるクエリがどんなものか追いたかったのですが、いずれの公式ドキュメントにも明記されていなかったため備忘録です。

https://docs.nestjs.com/recipes/prisma
https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/logging

なお、執筆環境の依存モジュールバージョンは以下の通りです。バージョン差異による不具合はご了承ください。

package.json
"dependencies": {
  "@nestjs/common": "^8.0.0",
  "@nestjs/core": "^8.0.0",
  "@prisma/client": "^3.11.1",
  "prisma": "^3.11.1"
}

Prisma on NestJS の雛形

NestJS で Prisma を利用する場合、インスタンス化せず以下の様に service として定義、各々の module で利用します。DB 接続を試行するライフサイクルイベントonModuleInitの定義は現在オプションですが、後続解説のため定義しておきます。

apps/api/src/prisma.service.ts
import {
  Injectable,
  OnModuleInit,
} from '@nestjs/common';
import { PrismaClient, Prisma } from '@prisma/client';

@Injectable()
export class PrismaService
  extends PrismaClient
  implements OnModuleInit
{
  async onModuleInit() {
    await this.$connect();
  }
}

Logger の設定

この雛形に対し、以下のとおり実装を追加すれば OK。

  • @nestjs/commonの Logger インスタンスを確保しておく
  • コンストラクタのsuperで log 出力を指定する
  • onModuleInitで、イベントリスナーを登録する

ランタイムの改修は上記 3 点で十分ですが、このままではthis.$onで型エラーが発生します。このエラーはextends PrismaClientextends PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>に修正することで、this.$onの型推論が拡張されます。

apps/api/src/prisma.service.ts
import {
  Injectable,
  OnModuleInit,
+ Logger,
} from '@nestjs/common';
import { PrismaClient, Prisma } from '@prisma/client';

@Injectable()
export class PrismaService
- extends PrismaClient
+ extends PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel> // <- here
  implements OnModuleInit
{
+ private readonly logger = new Logger(PrismaService.name);
+ constructor() {
+   super({ log: ['query', 'info', 'warn', 'error'] });
+ }
  async onModuleInit() {
+   this.$on('query', (event) => {
+     this.logger.log(
+       `Query: ${event.query}`,
+       `Params: ${event.params}`,
+       `Duration: ${event.duration} ms`,
+     );
+   });
+   this.$on('info', (event) => {
+     this.logger.log(`message: ${event.message}`);
+   });
+   this.$on('error', (event) => {
+     this.logger.log(`error: ${event.message}`);
+   });
+   this.$on('warn', (event) => {
+     this.logger.log(`warn: ${event.message}`);
+   });
    await this.$connect();
  }
}

これで発行されるクエリが出力される様になりました(例示は PostgreSQL によるもの)

prisma:query SELECT "public"."Post"."id", "public"."Post"."title", "public"."Post"."content", "public"."Post"."published", "public"."Post"."authorId" FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1) OFFSET $2
[Nest] 46661  - 04/01/2022, 1:02:35 PM     LOG [PrismaService] Query: SELECT "public"."Post"."id", "public"."Post"."title", "public"."Post"."content", "public"."Post"."published", "public"."Post"."authorId" FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1) OFFSET $2
[Nest] 46661  - 04/01/2022, 1:02:35 PM     LOG [PrismaService] Params: [3,0]
[Nest] 46661  - 04/01/2022, 1:02:35 PM     LOG [PrismaService] Duration: 3 ms

参考

https://stackoverflow.com/questions/67509194/logging-with-prisma-2-and-nestjs-dependency-injection-problem