跳到主要内容

OpenTelemetry 追踪

追踪提供了 Prisma Client 执行活动的详细日志,精确到操作级别,包括执行每个查询所需的时间。它帮助你分析应用程序的性能并识别瓶颈。追踪完全兼容 OpenTelemetry,因此你可以将其作为端到端应用程序追踪系统的一部分使用。

信息

追踪为你提供 Prisma ORM 项目高度详细的、操作级别的洞察。如果你想要聚合的数值报告,例如查询计数、连接计数和总查询执行时间,请参阅 指标

关于追踪

启用追踪后,Prisma Client 输出以下内容

  • Prisma Client 执行的每个操作(例如 findMany)对应一个追踪。
  • 在每个追踪中,包含一个或多个 span(跨度)。每个 span 代表操作的一个阶段所需的时间长度,例如序列化或数据库查询。Span 以树状结构表示,子 span 表示执行发生在更大的父 span 内。

追踪中的 span 数量和类型取决于该追踪涵盖的操作类型,示例结构如下

image

你可以将追踪输出发送到控制台,或在任何 OpenTelemetry 兼容的追踪系统中分析它,例如 JaegerHoneycombDatadog。在本页中,我们提供一个如何将追踪输出发送到 Jaeger 的示例,你可以在本地运行它。

追踪输出

对于每个追踪,Prisma Client 输出一系列 span。这些 span 的数量和类型取决于 Prisma Client 操作。典型的 Prisma 追踪包含以下 span

  • prisma:client:operation:代表整个 Prisma Client 操作,从 Prisma Client 到数据库再返回。它包含 Prisma Client 调用的模型和方法等详细信息。根据 Prisma 操作,它可能包含以下一个或多个 span
    • prisma:client:connect:代表 Prisma Client 连接到数据库所需的时间。
    • prisma:client:serialize:代表将 Prisma Client 操作验证并转换为 query engine 的查询所需的时间。
    • prisma:engine:query:代表查询在 query engine 中所需的时间。
      • prisma:engine:connection:代表 Prisma Client 获取数据库连接所需的时间。
      • prisma:engine:db_query:代表对数据库执行的数据库查询。它在标签中包含查询本身,以及查询运行所需的时间。
      • prisma:engine:serialize:代表将数据库的原始响应转换为类型化结果所需的时间。
      • prisma:engine:response_json_serialization:代表将数据库查询结果序列化为发送给 Prisma Client 的 JSON 响应所需的时间。

例如,给定以下 Prisma Client 代码

prisma.user.findMany({
where: {
email: email,
},
include: {
posts: true,
},
})

追踪结构如下

  • prisma:client:operation
    • prisma:client:serialize
    • prisma:engine:query
      • prisma:engine:connection
      • prisma:engine:db_query:第一个 SQL 查询或命令的详细信息...
      • prisma:engine:db_query:...下一个 SQL 查询或命令的详细信息...
      • prisma:engine:serialize
      • prisma:engine:response_json_serialization

考虑事项和先决条件

如果你的应用程序向 collector 发送大量 span,这可能会对性能产生显著影响。有关如何最小化此影响的信息,请参阅 降低性能影响

要使用追踪,你必须执行以下操作

  1. 安装相应的依赖项.
  2. 安装 OpenTelemetry 包.
  3. 在应用程序中注册追踪.

开始使用 Prisma ORM 中的追踪

本节介绍如何在应用程序中安装和注册追踪。

步骤 1. 安装最新版本的 Prisma ORM 依赖项

使用 6.1.0 或更高版本的 prisma@prisma/client@prisma/instrumentation npm 包。你还需要安装 @opentelemetry/api 包,因为它是对等依赖项。

npm install prisma@latest --save-dev
npm install @prisma/client@latest --save
npm install @prisma/instrumentation@latest --save
npm install @opentelemetry/api@latest --save
在早期版本的 Prisma ORM 中使用追踪

追踪作为预览功能在 Prisma ORM 的 4.2.0 版本中添加。对于 4.2.0 到 6.1.0 之间的 Prisma ORM 版本,你需要在 Prisma schema 文件中启用 tracing 预览功能。

generator client {
provider = "prisma-client-js"
previewFeatures = ["tracing"]
}

步骤 2:安装 OpenTelemetry 包

现在安装相应的 OpenTelemetry 包,如下所示

npm install @opentelemetry/semantic-conventions @opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-node @opentelemetry/resources

步骤 3:在应用程序中注册追踪

以下代码提供了在 Prisma 中配置 OpenTelemetry 追踪的两个示例

  1. 使用 @opentelemetry/sdk-trace-node(现有示例),它提供了对追踪设置的细粒度控制。
  2. 使用 @opentelemetry/sdk-node,它提供了更简单的配置,并与 OpenTelemetry 的 JavaScript 入门指南保持一致。

选项 1:使用 @opentelemetry/sdk-trace-node

此设置提供了对 instrumentation 和追踪的细粒度控制。你需要为你的特定应用程序定制此配置。对于需要快速设置以将追踪发送到 OTLP 兼容后端(例如 Honeycomb、Jaeger 或 Datadog)的用户来说,这种方法简洁且更容易。

// Imports
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
import { PrismaInstrumentation, registerInstrumentations } from '@prisma/instrumentation'
import { Resource } from '@opentelemetry/resources'

// Configure the trace provider
const provider = new NodeTracerProvider({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'example application', // Replace with your service name
[SEMRESATTRS_SERVICE_VERSION]: '0.0.1', // Replace with your service version
}),
})

// Configure how spans are processed and exported. In this case, we're sending spans
// as we receive them to an OTLP-compatible collector (e.g., Jaeger).
provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter()))

// Register your auto-instrumentors
registerInstrumentations({
tracerProvider: provider,
instrumentations: [new PrismaInstrumentation()],
})

// Register the provider globally
provider.register()

这种方法提供了最大的灵活性,但可能需要额外的配置步骤。

选项 2:使用 @opentelemetry/sdk-node

对于许多用户,尤其是初学者,NodeSDK 类通过将常见默认值打包到单个统一配置中来简化 OpenTelemetry 设置。

// Imports
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { PrismaInstrumentation } from '@prisma/instrumentation'

// Configure the OTLP trace exporter
const traceExporter = new OTLPTraceExporter({
url: 'https://api.honeycomb.io/v1/traces', // Replace with your collector's endpoint
headers: {
'x-honeycomb-team': 'HONEYCOMB_API_KEY', // Replace with your Honeycomb API key or collector auth header
},
})

// Initialize the NodeSDK
const sdk = new NodeSDK({
serviceName: 'my-service-name', // Replace with your service name
traceExporter,
instrumentations: [
new PrismaInstrumentation({
middleware: true, // Enable middleware tracing if needed
}),
],
})

// Start the SDK
sdk.start()

// Handle graceful shutdown
process.on('SIGTERM', async () => {
try {
await sdk.shutdown()
console.log('Tracing shut down successfully')
} catch (err) {
console.error('Error shutting down tracing', err)
} finally {
process.exit(0)
}
})

如果以下情况,请选择 NodeSDK 方法

  • 你刚开始接触 OpenTelemetry 并想要简化设置。
  • 你需要以最少的样板代码快速集成追踪。
  • 你正在使用 OTLP 兼容的追踪后端,例如 Honeycomb、Jaeger 或 Datadog。

如果以下情况,请选择 NodeTracerProvider 方法

  • 你需要详细控制 span 如何创建、处理和导出。
  • 你正在使用自定义的 span processor 或 exporter。
  • 你的应用程序需要特定的 instrumentation 或采样策略。

OpenTelemetry 具有高度可配置性。你可以自定义 resource attributes、哪些组件被 instrument、span 如何处理以及 span 发送到哪里。

你可以在 此示例应用程序 中找到一个包含指标的完整示例。

追踪操作指南

使用 Jaeger 可视化追踪

Jaeger 是一个免费的开源 OpenTelemetry collector 和仪表盘,你可以使用它来可视化你的追踪。

以下截图显示了一个追踪可视化的示例

Jaeger UI

要在本地运行 Jaeger,请使用以下 Docker 命令

docker run --rm --name jaeger -d -e COLLECTOR_OTLP_ENABLED=true -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest

你现在可以在 http://localhost:16686/ 找到追踪仪表盘。启用追踪后使用你的应用程序时,你将开始在此仪表盘中看到追踪。

将追踪输出发送到控制台

以下示例使用 @opentelemetry/sdk-trace-base 中的 ConsoleSpanExporter 将追踪输出发送到控制台。

// Imports
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import {
BasicTracerProvider,
ConsoleSpanExporter,
SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-base'
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'
import * as api from '@opentelemetry/api'
import { PrismaInstrumentation, registerInstrumentations } from '@prisma/instrumentation'
import { Resource } from '@opentelemetry/resources'

// Export the tracing
export function otelSetup() {
const contextManager = new AsyncHooksContextManager().enable()

api.context.setGlobalContextManager(contextManager)

//Configure the console exporter
const consoleExporter = new ConsoleSpanExporter()

// Configure the trace provider
const provider = new BasicTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'test-tracing-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
}),
})

// Configure how spans are processed and exported. In this case we're sending spans
// as we receive them to the console
provider.addSpanProcessor(new SimpleSpanProcessor(consoleExporter))

// Register your auto-instrumentors
registerInstrumentations({
tracerProvider: provider,
instrumentations: [new PrismaInstrumentation()],
})

// Register the provider
provider.register()
}

追踪 Prisma Client middleware

默认情况下,追踪不会为 Prisma Client middleware 输出 span。要将你的 middleware 包含在追踪中,请在 registerInstrumentations 语句中将 middleware 设置为 true,如下所示

registerInstrumentations({
instrumentations: [new PrismaInstrumentation({ middleware: true })],
})

这将为你的追踪添加以下 span 类型

  • prisma:client:middleware:代表操作在你的 middleware 中花费的时间。

追踪交互式事务

执行交互式事务时,除了 标准 span 之外,你还会看到以下 span

  • prisma:client:transaction:包裹 prisma span 的 根 span
    • prisma:engine:itx_runner:代表交互式事务在 query engine 中所需的时间。
    • prisma:engine:itx_query_builder:代表构建交互式事务所需的时间。

例如,以下 Prisma schema

schema.prisma
generator client {
provider = "prisma-client-js"
}

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

model User {
id Int @id @default(autoincrement())
email String @unique
}

model Audit {
id Int @id
table String
action String
}

给定以下交互式事务

await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: {
email: email,
},
})

await tx.audit.create({
data: {
table: 'user',
action: 'create',
id: user.id,
},
})

return user
})

追踪结构如下

  • prisma:client:transaction
  • prisma:client:connect
  • prisma:engine:itx_runner
    • prisma:engine:connection
    • prisma:engine:db_query
    • prisma:engine:itx_query_builder
      • prisma:engine:db_query
      • prisma:engine:db_query
      • prisma:engine:serialize
    • prisma:engine:itx_query_builder
      • prisma:engine:db_query
      • prisma:engine:db_query
      • prisma:engine:serialize
  • prisma:client:operation
    • prisma:client:serialize
  • prisma:client:operation
    • prisma:client:serialize

添加更多 instrumentation

OpenTelemetry 的一个很好的好处是,只需对应用程序代码进行最少的更改,即可添加更多 instrumentation。

例如,要添加 HTTP 和 ExpressJS 追踪,请将以下 instrumentation 添加到你的 OpenTelemetry 配置中。这些 instrumentation 为完整的请求-响应生命周期添加 span。这些 span 显示你的 HTTP 请求所需的时间。

// Imports
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'

// Register your auto-instrumentors
registerInstrumentations({
tracerProvider: provider,
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation(),
new PrismaInstrumentation(),
],
})

有关可用 instrumentation 的完整列表,请查看 OpenTelemetry Registry

自定义 resource attributes

你可以通过更改 resource attributes 来调整应用程序追踪的分组方式,使其更具体地反映你的应用程序

const provider = new NodeTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'weblog',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
}),
})

目前正在努力标准化常见的 resource attributes。在可能的情况下,最好遵循 标准属性名称

降低性能影响

如果你的应用程序向 collector 发送大量 span,这可能会对性能产生显著影响。你可以使用以下方法来降低此影响

使用 BatchSpanProcessor 批量发送追踪

在生产环境中,你可以使用 OpenTelemetry 的 BatchSpanProcessor 将 span 批量发送到 collector,而不是逐个发送。然而,在开发和测试期间,你可能不希望批量发送 span。在这种情况下,你可能更喜欢使用 SimpleSpanProcessor

你可以根据环境配置你的追踪设置,以使用相应的 span processor,如下所示

import {
SimpleSpanProcessor,
BatchSpanProcessor,
} from '@opentelemetry/sdk-trace-base'

if (process.env.NODE_ENV === 'production') {
provider.addSpanProcessor(new BatchSpanProcessor(otlpTraceExporter))
} else {
provider.addSpanProcessor(new SimpleSpanProcessor(otlpTraceExporter))
}

通过采样向 collector 发送更少的 span

降低性能影响的另一种方法是使用 概率采样 向 collector 发送更少的 span。这降低了追踪的收集成本,但仍然能很好地反映应用程序中发生的情况。

示例实现如下

import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
import { TraceIdRatioBasedSampler } from '@opentelemetry/core'
import { Resource } from '@opentelemetry/resources'

const provider = new NodeTracerProvider({
sampler: new TraceIdRatioBasedSampler(0.1),
resource: new Resource({
// we can define some metadata about the trace resource
[SemanticResourceAttributes.SERVICE_NAME]: 'test-tracing-service',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
}),
})

追踪故障排除

我的追踪未显示

设置追踪的顺序很重要。在你的应用程序中,确保在导入任何被 instrument 的依赖项之前注册追踪和 instrumentation。例如

import { registerTracing } from './tracing'

registerTracing({
name: 'tracing-example',
version: '0.0.1',
})

// You must import any dependencies after you register tracing.
import { PrismaClient } from '@prisma/client'
import async from 'express-async-handler'
import express from 'express'