跳至主要内容

如何使用 Prisma ORM 设置 Datadog 追踪

15 分钟

介绍

在本指南中,你将学习如何为新的 Prisma 项目设置 Datadog 追踪。通过将 @prisma/instrumentation 包与 Prisma 客户端扩展结合使用,你可以捕获每个数据库查询的详细 span。这些 span 会通过 Datadog 的官方 Node.js APM 库 dd-trace 附带查询元数据并发送到 Datadog,从而能够监控、分析和深入了解应用程序的数据库活动。

什么是 span 和追踪?

  • Span 是分布式系统或复杂应用程序中的各个操作或工作单元。每个数据库查询、服务调用或外部请求都由一个 span 表示。

  • 追踪 将这些 span 关联起来,形成请求生命周期的完整端到端视图。通过追踪,你可以可视化瓶颈、识别有问题查询以及精确定位查询中发生错误的位置。

为什么将 Datadog 与 Prisma ORM 结合使用?

Datadog 提供应用程序性能监控 (APM)、指标、日志和仪表板,帮助你观察和调试生产系统。

虽然 Prisma ORM 抽象了 SQL 并提高了开发人员的生产力,但如果缺少适当的检测,它可能会模糊查询性能。通过使用 @prisma/instrumentationdd-trace 将 Datadog 与 Prisma 集成,你可以自动捕获每个数据库查询的 span。

这使你能够:

  • 衡量每个查询的延迟。
  • 检查查询参数和原始 SQL。
  • 在应用程序级请求的上下文中追踪 Prisma 操作。
  • 识别与数据库访问相关的瓶颈。

这种集成以最少的努力提供了 Prisma 查询的运行时可见性,帮助你实时捕获慢查询和错误。

先决条件

开始之前,请确保你已拥有以下内容:

  • 已安装 Node.js(推荐 v18+)。
  • 本地或托管的 PostgreSQL 数据库。
  • 一个 Datadog 帐户。如果你没有,在此注册
  • Datadog Agent 已安装并运行在你将运行此应用程序的机器或服务器上。你可以按照 Datadog Agent 安装文档 进行设置。

1. 创建新项目

我们将通过创建一个新的 Node.js 项目来演示 Datadog 和 Prisma ORM 的追踪。这将是一个最小的、独立的设置,专注于运行和追踪 Prisma 查询,以单独理解检测流程。

如果你要将追踪集成到现有的 Prisma 项目中,你可以跳过此步骤,直接从设置追踪部分开始。只需确保在你的项目对应的文件夹结构中应用更改。

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

在此设置中,你将:

  • 定义一个包含基本模型的 Prisma schema。
  • 连接到 Postgres 数据库(Prisma Postgres 或你自己的)。
  • 使用 @prisma/instrumentationdd-trace 为所有查询配置 Datadog 追踪。
  • 运行一个执行 Prisma 操作并将 span 发送到 Datadog 的示例脚本。

2. 设置 Prisma ORM

在本节中,你将安装 Prisma,创建你的 schema,并生成 Prisma 客户端。这可以为你的应用程序运行数据库查询做好准备——这些查询将通过 Datadog 进行追踪。

2.1. 安装并初始化 Prisma ORM

运行以下命令来安装 Prisma 和一个最小的 TypeScript 运行器

npm install -D prisma tsx

然后使用 --db 标志初始化 Prisma,以创建 新的 Prisma Postgres 实例

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

系统将提示你为数据库命名并选择最近的区域。为了清晰起见,请选择一个易记的名称(例如,My Datadog Project)。

此命令执行以下操作:

  • 创建一个包含 schema.prisma 文件的 prisma 目录。
  • /src/generated/prisma 目录中生成 Prisma 客户端(如 --output 标志所指定)。
  • 在项目根目录中创建一个包含数据库连接字符串(DATABASE_URL)的 .env 文件。

.env 文件应包含标准连接字符串

.env
# Placeholder url you have to replace
DATABASE_URL="postgresql://janedoe:mypassword@localhost:5432/mydb?schema=sample"

安装 PostgreSQL 的驱动程序适配器

npm i @prisma/adapter-pg pg
npm i -D @types/pg
信息

如果你使用的是其他数据库提供程序(MySQL、SQL Server、SQLite),请安装相应的驱动程序适配器包,而不是 @prisma/adapter-pg。有关更多信息,请参阅 数据库驱动程序

2.2. 定义模型

现在,打开 prisma/schema.prisma 并更新你的生成器块和模型。将 generator 块替换为以下内容,并添加 UserPost 模型

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

datasource db {
provider = "postgresql"
}

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])
}

创建一个 prisma.config.ts 文件来配置 Prisma

prisma.config.ts
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config';

export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
},
datasource: {
url: env('DATABASE_URL'),
},
});
注意

如果你尚未安装 dotenv 包,则需要安装它

npm install dotenv

2.3. 生成 Prisma 客户端并运行迁移

生成 Prisma 客户端并将你的 schema 应用到数据库

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

这将根据你在 Postgres 数据库中的 schema 创建表,并生成一个客户端供你与数据库交互。

3. 安装追踪所需的依赖项

除了 Prisma,你还需要以下包用于 Datadog 追踪

npm install @prisma/instrumentation \
dd-trace

还要确保你拥有 TypeScript 的开发依赖项

npm install -D typescript

以下是快速概述:

  • @prisma/instrumentation:检测 Prisma 查询,使它们在追踪器中显示为 span。
  • 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";

tracer.init({
profiling: true,
logInjection: true,
runtimeMetrics: true,
dbmPropagationMode: "full",
env: "dev",
sampleRate: 1,
service: "prisma-datadog-tracing",
version: "1.0.0"
});

export { tracer };

解释

  • tracer.init 使用 service 名称配置 dd-trace。此名称会显示在 Datadog 的 APM > Services 列表中。
  • @prisma/instrumentation 自动将每个 Prisma 查询记录为 Datadog span。

5. 实例化 Prisma 并运行查询

5.1. 创建 Prisma 客户端实例

创建一个 src/client.ts 来保存你的 Prisma 客户端实例化

src/client.ts
import { tracer } from "./tracer";
import { PrismaClient } from "./generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL!,
});

const prisma = new PrismaClient({
adapter,
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;
}
},
},
});

export { prisma };

上述设置让你对查询的追踪方式有更多控制权

  • 通过在创建 Prisma 客户端之前导入 tracer,追踪被尽可能早地初始化。
  • $on("query") 钩子捕获原始 SQL 查询并将其作为独立 span 发送。
  • $allOperations 扩展将所有 Prisma 操作封装在自定义 span 中,允许你使用模型、操作类型和参数等元数据标记它们。

与提供开箱即用自动追踪的 @prisma/instrumentation 包不同,这种手动设置使你能够完全控制每个 span 的结构和标记方式。当你需要自定义 span 名称、额外元数据、更简单的设置,或者在 OpenTelemetry 生态系统中解决限制或兼容性问题时,它会很有帮助。它还允许你根据查询上下文调整追踪行为,这在复杂应用程序中特别有用。

5.2. 添加执行查询的脚本

创建一个 src/index.ts 文件并添加代码以执行对数据库的查询并将追踪发送到 Datadog

src/index.ts
import { tracer } from "./tracer";
import {
PrismaInstrumentation,
registerInstrumentations,
} from "@prisma/instrumentation";
import { prisma } from "./client";

const provider = new tracer.TracerProvider();

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

provider.register();

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.");
});
注意

如果由于 @opentelemetry/api 包的版本不匹配导致 tracerProvider: provider 行出现 linting 错误,则很可能是由于版本不匹配。

要解决此问题,请在 package.json 中添加以下覆盖

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

这是必要的,因为 dd-trace 尚不支持 @opentelemetry/api1.9.0 或更高版本

更新 package.json 后,重新安装你的依赖项

npm i

这应该会解决 linting 错误。

6. 运行查询并查看追踪

运行查询

npx tsx src/index.ts

这将执行你的脚本,该脚本:

  • 注册 Datadog 追踪器。
  • 执行多个 Prisma 查询。
  • 记录每个操作的结果。

然后,在 Datadog 中确认追踪

  • 打开你的 Datadog APM 页面
  • 在侧面面板中导航到 APM > Traces > Explorer
  • 浏览追踪和 span 列表,每个列表都代表一个 Prisma 查询(例如 prisma:query)。
信息

根据你的 Datadog 设置,新数据可能需要一两分钟才能出现。如果你没有立即看到追踪,请刷新或稍等片刻。

后续步骤

你已成功:

  • 创建了一个带有 Prisma Postgres 的 Prisma ORM 项目。
  • 使用 @prisma/instrumentationdd-trace 设置了 Datadog 追踪。
  • 验证了数据库操作在 Datadog 中显示为 span。

为了进一步提高你的可观测性:

  • 为你的 HTTP 服务器或其他服务(例如 Express、Fastify)添加更多检测。
  • 创建仪表板以查看 你数据的关键指标。

如需更多指导,请查看:


与 Prisma 保持联系

通过以下方式与我们保持联系,继续你的 Prisma 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发人员协作。

我们真诚地感谢你的参与,并期待你成为我们社区的一部分!

© . This site is unofficial and not affiliated with Prisma Data, Inc.