メインコンテンツへスキップ

Prisma ORMでDatadogトレースを設定する方法

15分

はじめに

このガイドでは、新しいPrismaプロジェクトでDatadogトレースを設定する方法を学びます。@prisma/instrumentationパッケージとPrisma Client拡張機能を組み合わせることで、すべてのデータベースクエリの詳細なスパンをキャプチャできます。これらのスパンは、クエリメタデータで強化され、DatadogのNode.js向け公式APMライブラリであるdd-traceを使用してDatadogに送信されます。これにより、アプリケーションのデータベースアクティビティを監視、分析し、可視化できます。

スパンとトレースとは?

  • スパンは、分散システムまたは複雑なアプリケーション内の個々の操作または作業単位です。各データベースクエリ、サービス呼び出し、または外部リクエストはスパンによって表されます。

  • トレースは、これらのスパンを結びつけて、リクエストのライフサイクルの完全なエンドツーエンドの全体像を形成します。トレースを使用すると、ボトルネックを視覚化し、問題のあるクエリを特定し、クエリからエラーが発生する場所を正確に特定できます。

Prisma ORMでDatadogを使用する理由

Datadogは、アプリケーションパフォーマンス監視 (APM)、メトリクス、ログ、ダッシュボードを提供し、本番システムの監視とデバッグを支援します。

Prisma ORMはSQLを抽象化し、開発者の生産性を向上させますが、適切なインストゥルメンテーションなしではクエリパフォーマンスを不明瞭にする可能性があります。Datadogを@prisma/instrumentationdd-traceを使用してPrismaと統合することで、すべてのデータベースクエリのスパンを自動的にキャプチャできます。

これにより、次のことが可能になります。

  • クエリごとのレイテンシを測定する。
  • クエリ引数と生のSQLを検査する。
  • アプリケーションレベルのリクエストのコンテキストでPrisma操作をトレースする。
  • データベースアクセスに関連するボトルネックを特定する。

この統合により、最小限の労力でPrismaクエリのランタイム可視性が提供され、低速なクエリやエラーをリアルタイムで捕捉するのに役立ちます。

前提条件

始める前に、以下のものがあることを確認してください。

  • Node.jsがインストールされていること(v18以上を推奨)。
  • ローカルまたはホストされたPostgreSQLデータベース。
  • Datadogアカウント。お持ちでない場合は、こちらでサインアップしてください
  • このアプリケーションを実行するマシンまたはサーバーにDatadog Agentがインストールされ、実行されていること。Datadog Agentのインストール手順に従って設定できます。

1. 新しいプロジェクトを作成する

まず、DatadogとPrisma ORMでのトレースをデモンストレーションするために、新しいNode.jsプロジェクトを作成します。これは、Prismaクエリの実行とトレースに焦点を当てた、最小限のスタンドアロンセットアップであり、インストゥルメンテーションフローを単独で理解するためのものです。

既存のPrismaプロジェクトにトレースを統合する場合、この手順はスキップして、トレース設定セクションから直接進むことができます。プロジェクトの同等のフォルダ構造に変更を適用していることを確認してください。

mkdir prisma-datadog-tracing
cd prisma-datadog-tracing
npm init -y

このセットアップでは、次のことを行います。

  • 基本的なモデルを持つPrismaスキーマを定義する。
  • Postgresデータベースに接続する(Prisma Postgresまたは独自のデータベース)。
  • @prisma/instrumentationdd-traceを使用して、すべてのクエリのDatadogトレースを設定する。
  • Prisma操作を実行し、スパンをDatadogに送信するサンプルスクリプトを実行する。

2. Prisma ORMをセットアップする

このセクションでは、Prismaをインストールし、スキーマを作成し、Prisma Clientを生成します。これにより、Datadogでトレースするデータベースクエリを実行するためのアプリケーションが準備されます。

2.1. Prisma ORMのインストールと初期化

Prismaと最小限のTypeScriptランナーをインストールするには、以下のコマンドを実行してください。

npm install -D prisma tsx

次に、Prismaを初期化します。

注意

プロジェクトでPrismaを初期化する際に、--dbフラグを使用して新しいPrisma Postgresインスタンスを作成できます。


npx prisma init --db --output ../src/generated/prisma
注意

データベースの名前と最も近いリージョンの選択を求められます。わかりやすくするために、覚えやすい名前(例: My Datadog Project)を選択してください。

このコマンドは以下のことを行います。

  • prismaディレクトリとschema.prismaファイルを作成します。
  • /src/generated/prismaディレクトリにPrisma Clientを生成します(--outputフラグで指定されたとおり)。
  • プロジェクトのルートに、データベース接続文字列(DATABASE_URL)を含む.envファイルを作成します。

--dbフラグを使用しなかった場合は、.envファイル内のプレースホルダーデータベースURLを置き換えてください。

.env
DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=..." 

Prisma Postgresを使用している場合は、以下もインストールしてください。

npm i @prisma/extension-accelerate

この拡張機能により、Prismaクエリをキャッシュすることができます。

2.2. モデルを定義する

次に、prisma/schema.prismaを開き、ジェネレーターブロックとモデルを更新します。generatorブロックを以下に置き換え、UserモデルとPostモデルを追加してください。

prisma/schema.prisma
generator client {
provider = "prisma-client-js"
output = "../src/generated/prisma"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
}

2.3. Prisma Clientを生成し、マイグレーションを実行する

Prisma Clientを生成し、スキーマをデータベースに適用してください。

npx prisma generate
npx prisma migrate dev --name "init"

これにより、Postgresデータベースにスキーマに応じたテーブルが作成され、データベースと対話するためのクライアントが生成されます。

3. トレースに必要な依存関係をインストールする

Prismaに加えて、Datadogトレースには以下のパッケージが必要です。

npm install @prisma/instrumentation \
dd-trace

また、TypeScriptの開発依存関係があることを確認してください。

npm install -D typescript

概要は以下のとおりです。

  • @prisma/instrumentation: Prismaクエリをインストゥルメント化し、トレーサーでスパンとして表示されるようにします。
  • dd-trace: Datadogの公式Node.jsトレースライブラリ。

4. Datadogトレースをセットアップする

srcフォルダにtracer.tsファイルを作成し、トレースロジックをインスタンス化します。

touch src/tracer.ts

4.1. トレーサーを構成する

src/tracer.tsを開き、以下のコードを追加します。

src/tracer.ts
import Tracer from "dd-trace";
import {
PrismaInstrumentation,
registerInstrumentations,
} from "@prisma/instrumentation";

const tracer = Tracer.init({
apmTracingEnabled: true,
service: "prisma-datadog-tracing",
version: "1.0.0",
profiling: true
});

const provider = new tracer.TracerProvider();

// Register the provider globally
provider.register();

registerInstrumentations({
instrumentations: [
new PrismaInstrumentation({
enabled: true,
}),
],
tracerProvider: provider,
});

export { tracer };
注意

traceProvider: providerの行で型が互換性がないというLintingエラーが発生した場合、それはおそらく@opentelemetry/apiパッケージのバージョン不一致が原因です。

これを解決するには、package.jsonに以下のオーバーライドを追加してください。

"overrides": {
"@opentelemetry/api": "1.8.0"
}

これは、dd-traceがまだ@opentelemetry/apiのバージョン1.9.0以上をサポートしていないためです。

package.jsonを更新した後、依存関係を再インストールしてください。

npm i

これにより、Lintingエラーが解決されるはずです。

説明

  • Tracer.initは、dd-traceservice名で構成します。この名前は、DatadogのAPM > Servicesリストに表示されます。
  • @prisma/instrumentationは、各PrismaクエリをDatadogスパンとして自動的に記録します。
  • middleware: trueオプションは、各クエリがインストゥルメンテーションのためにインターセプトされることを保証します。

5. Prismaをインスタンス化してクエリを実行する

5.1. Prisma Clientインスタンスを作成する

src/client.tsを作成して、Prisma Clientのインスタンス化を保持します。

src/client.ts
import { tracer } from "./tracer";
import { withAccelerate } from "@prisma/extension-accelerate";
import { PrismaClient } from "./generated/prisma";

const prisma = new PrismaClient({
log: [{ emit: "event", level: "query" }],
})
.$on("query", (e) => {
const span = tracer.startSpan(`prisma_raw_query`, {
childOf: tracer.scope().active() || undefined,
tags: {
"prisma.rawquery": e.query,
},
});
span.finish();
})
.$extends({
query: {
async $allOperations({ operation, model, args, query }) {
const span = tracer.startSpan(
`prisma_query_${model?.toLowerCase()}_${operation}`,
{
tags: {
"prisma.operation": operation,
"prisma.model": model,
"prisma.args": JSON.stringify(args),
"prisma.rawQuery": query,
},
childOf: tracer.scope().active() || undefined,
}
);

try {
const result = await query(args);
span.finish();
return result;
} catch (error) {
span.setTag("error", error);
span.finish();
throw error;
}
},
},
})
.$extends(withAccelerate());

export { prisma };

上記のセットアップにより、クエリのトレース方法をより細かく制御できます。

  • トレースは、Prisma Clientを作成する前にtracerをインポートすることで、できるだけ早く初期化されます。
  • $on("query")フックは、生のSQLクエリをキャプチャし、それらをスタンドアロンスパンとして送信します。
  • $allOperations拡張機能は、すべてのPrisma操作をカスタムスパンでラップし、モデル、操作タイプ、引数などのメタデータでタグ付けできるようにします。

自動トレースを提供する@prisma/instrumentationパッケージとは異なり、この手動設定では、各スパンの構造とタグ付けを完全に制御できます。カスタムスパン名、追加のメタデータ、よりシンプルなセットアップが必要な場合、またはOpenTelemetryエコシステムの制限や互換性の問題に対処する場合に役立ちます。また、クエリコンテキストに基づいてトレース動作を適応させることもでき、これは複雑なアプリケーションで特に有用です。

5.2. クエリを実行するスクリプトを追加する

src/index.tsファイルを作成し、データベースへのクエリを実行し、Datadogにトレースを送信するコードを追加します。

src/index.ts
import { prisma } from "./client";

async function main() {
const user1Email = `alice${Date.now()}@prisma.io`;
const user2Email = `bob${Date.now()}@prisma.io`;

let alice, bob;

// 1. Create users concurrently
try {
[alice, bob] = await Promise.all([
prisma.user.create({
data: {
email: user1Email,
name: "Alice",
posts: {
create: {
title: "Join the Prisma community on Discord",
content: "https://pris.ly/discord",
published: true,
},
},
},
include: { posts: true },
}),
prisma.user.create({
data: {
email: user2Email,
name: "Bob",
posts: {
create: [
{
title: "Check out Prisma on YouTube",
content: "https://pris.ly/youtube",
published: true,
},
{
title: "Follow Prisma on Twitter",
content: "https://twitter.com/prisma/",
published: false,
},
],
},
},
include: { posts: true },
}),
]);
console.log(
`✅ Created users: ${alice.name} (${alice.posts.length} post) and ${bob.name} (${bob.posts.length} posts)`
);
} catch (err) {
console.error("❌ Error creating users:", err);
return;
}

// 2. Fetch all published posts
try {
const publishedPosts = await prisma.post.findMany({
where: { published: true },
});
console.log(`✅ Retrieved ${publishedPosts.length} published post(s).`);
} catch (err) {
console.error("❌ Error fetching published posts:", err);
}

// 3. Create & publish a post for Alice
let post;
try {
post = await prisma.post.create({
data: {
title: "Join the Prisma Discord community",
content: "https://pris.ly/discord",
published: false,
author: { connect: { email: user1Email } },
},
});
console.log(`✅ Created draft post for Alice (ID: ${post.id})`);
} catch (err) {
console.error("❌ Error creating draft post for Alice:", err);
return;
}

try {
post = await prisma.post.update({
where: { id: post.id },
data: { published: true },
});
console.log("✅ Published Alice’s post:", post);
} catch (err) {
console.error("❌ Error publishing Alice's post:", err);
}

// 4. Fetch all posts by Alice
try {
const alicePosts = await prisma.post.findMany({
where: { author: { email: user1Email } },
});
console.log(
`✅ Retrieved ${alicePosts.length} post(s) by Alice.`,
alicePosts
);
} catch (err) {
console.error("❌ Error fetching Alice's posts:", err);
}
}

// Entrypoint
main()
.catch((err) => {
console.error("❌ Unexpected error:", err);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
console.log("🔌 Disconnected from database.");
});

6. クエリを実行し、トレースを確認する

クエリを実行します。

npx tsx src/index.ts

これにより、以下のスクリプトが実行されます。

  • Datadogトレーサーを登録します。
  • 複数のPrismaクエリを実行します。
  • 各操作の結果をログに記録します。

次に、Datadogでトレースを確認します。

  • Datadog APMページを開きます。
  • サイドパネルでAPM > Traces > Explorerに移動します。
  • トレースとスパンのリストを探索してください。それぞれがPrismaクエリを表しています(例: prisma:query)。
情報

Datadogの設定によっては、新しいデータが表示されるまでに1〜2分かかる場合があります。すぐにトレースが表示されない場合は、更新するか、しばらくお待ちください。

次のステップ

正常に次のことを行いました。

  • Prisma Postgresを使用してPrisma ORMプロジェクトを作成しました。
  • @prisma/instrumentationdd-traceを使用してDatadogトレースを設定しました。
  • データベース操作がDatadogにスパンとして表示されることを確認しました。

さらに可観測性を向上させるには、

追加のガイダンスについては、以下を参照してください。


Prismaとのつながりを維持する

以下とつながり、Prismaの旅を続けてください。 活発なコミュニティに参加しましょう。情報収集し、関与し、他の開発者と協力してください。

  • Xでフォローする お知らせ、ライブイベント、役立つヒントについては。
  • Discordに参加する 質問したり、コミュニティと話したり、会話を通じて活発なサポートを受けたりできます。
  • YouTubeを購読する チュートリアル、デモ、ストリームについては。
  • GitHubで関与する リポジトリにスターを付けたり、問題を報告したり、問題に貢献したりしてください。
皆様のご参加を心より歓迎し、コミュニティの一員としてお迎えできることを楽しみにしております!

© . All rights reserved.