OpenTelemetry トレーシング
トレーシングは、Prisma Clientが実行するアクティビティ(各クエリの実行にかかった時間を含む)を操作レベルで詳細にログとして提供します。これにより、アプリケーションのパフォーマンスを分析し、ボトルネックを特定するのに役立ちます。トレーシングはOpenTelemetryに完全に準拠しているため、エンドツーエンドのアプリケーショントレーシングシステムの一部として使用できます。
トレーシングは、Prisma ORMプロジェクトの操作レベルでの非常に詳細な洞察を提供します。クエリ数、接続数、合計クエリ実行時間などの集計された数値レポートが必要な場合は、「メトリクス」を参照してください。
トレーシングについて
トレーシングを有効にすると、Prisma Clientは以下を出力します
- Prisma Clientが行う各操作(例:findMany)に対する1つのトレース。
- 各トレースには、1つ以上のスパンが含まれます。各スパンは、シリアライズやデータベースクエリなど、操作の1つのステージにかかる時間を表します。スパンはツリー構造で表現され、子スパンはより大きな親スパン内で実行されていることを示します。
トレース内のスパンの数と種類は、トレースがカバーする操作の種類によって異なりますが、例は以下のとおりです
トレーシング出力をコンソールに送信するか、Jaeger、Honeycomb、Datadogなど、OpenTelemetry互換のあらゆるトレーシングシステムで分析できます。このページでは、ローカルで実行できるJaegerにトレーシング出力を送信する方法の例を挙げます。
トレース出力
各トレースについて、Prisma Clientは一連のスパンを出力します。これらのスパンの数と種類は、Prisma Clientの操作によって異なります。一般的なPrismaトレースには、次のスパンが含まれます
prisma:client:operation
: Prisma Clientからデータベースへの、そしてデータベースからのPrisma Client操作全体を表します。Prisma Clientが呼び出したモデルやメソッドなどの詳細が含まれます。Prismaの操作に応じて、以下の1つ以上のスパンが含まれますprisma:client:connect
: Prisma Clientがデータベースに接続するのにかかる時間を表します。prisma:client:serialize
: Prisma Clientの操作をクエリエンジン用のクエリに検証・変換するのにかかる時間を表します。prisma:engine:query
: クエリエンジンでのクエリにかかる時間を表します。prisma:engine:connection
: Prisma Clientがデータベース接続を取得するのにかかる時間を表します。prisma:engine:db_query
: データベースに対して実行されたデータベースクエリを表します。タグにクエリと、クエリの実行にかかった時間が含まれます。prisma:engine:serialize
: データベースからの生の結果を型付けされた結果に変換するのにかかる時間を表します。prisma:engine:response_json_serialization
: データベースクエリの結果をJSONレスポンスとしてPrisma Clientにシリアライズするのにかかる時間を表します。
例えば、次の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
考慮事項と前提条件
アプリケーションが多数のスパンをコレクターに送信する場合、これはパフォーマンスに大きな影響を与える可能性があります。この影響を最小限に抑える方法については、「パフォーマンスの影響を軽減する」を参照してください。
トレーシングを使用するには、以下を行う必要があります
Prisma ORMでトレーシングを始める
このセクションでは、アプリケーションにトレーシングをインストールおよび登録する方法について説明します。
ステップ1. 最新のPrisma ORM依存関係をインストールする
prisma
、@prisma/client
、@prisma/instrumentation
npm パッケージのバージョン 6.1.0
以降を使用してください。また、ピア依存関係であるため、@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
でプレビュー機能として追加されました。Prisma ORMのバージョン4.2.0
から6.1.0
の間では、Prismaスキーマファイルで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トレーシングを構成する2つの例を提供します
@opentelemetry/sdk-trace-node
(既存の例) を使用。これにより、トレーシングのセットアップを細かく制御できます。@opentelemetry/sdk-node
を使用。これにより、よりシンプルな構成が提供され、OpenTelemetryのJavaScript入門ガイドに沿ったものになります。
オプション1: `@opentelemetry/sdk-trace-node`を使用する
このセットアップにより、インストゥルメンテーションとトレーシングを細かく制御できます。特定のアプリケーションに合わせてこの構成をカスタマイズする必要があります。このアプローチは簡潔で、Honeycomb、Jaeger、DatadogなどのOTLP互換バックエンドにトレースを迅速に送信するための簡単なセットアップを必要とするユーザーにとってより簡単です。
// 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を始めたばかりで、簡素化されたセットアップが必要な場合。
- 最小限のボイラープレートでトレーシングを迅速に統合する必要がある場合。
- Honeycomb、Jaeger、DatadogなどのOTLP互換のトレーシングバックエンドを使用している場合。
NodeTracerProvider
アプローチを選択する場合
- スパンがどのように作成、処理、エクスポートされるかを詳細に制御する必要がある場合。
- カスタムスパンプロセッサまたはエクスポータを使用している場合。
- アプリケーションに特定のインストゥルメンテーションまたはサンプリング戦略が必要な場合。
OpenTelemetryは高度に設定可能です。リソース属性、どのコンポーネントがインストゥルメンテーションされるか、スパンがどのように処理されるか、スパンがどこに送信されるかをカスタマイズできます。
メトリクスを含む完全な例は、このサンプルアプリケーションで見つけることができます。
トレーシングのハウツー
Jaegerでトレースを可視化する
Jaegerは、トレースを可視化するために使用できる、無料のオープンソースOpenTelemetryコレクターおよびダッシュボードです。
以下のスクリーンショットは、トレースの可視化例を示しています
Jaegerをローカルで実行するには、次のDockerコマンドを使用します
docker run --rm --name jaeger -d -e COLLECTOR_OTLP_ENABLED=true -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest
これで、https://: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ミドルウェアをトレースする
デフォルトでは、トレーシングはPrisma Clientミドルウェアのスパンを出力しません。ミドルウェアをトレースに含めるには、registerInstrumentations
ステートメントで middleware
を true
に設定します。
registerInstrumentations({
instrumentations: [new PrismaInstrumentation({ middleware: true })],
})
これにより、トレースに次のスパンタイプが追加されます
prisma:client:middleware
: 操作がミドルウェアで費やした時間を表します。
インタラクティブトランザクションをトレースする
インタラクティブトランザクションを実行すると、標準スパンに加えて以下のスパンが表示されます
prisma:client:transaction
:prisma
スパンをラップするルートスパン。prisma:engine:itx_runner
: クエリエンジンでインタラクティブトランザクションにかかる時間を表します。prisma:engine:itx_query_builder
: インタラクティブトランザクションを構築するのにかかる時間を表します。
例として、次の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
さらなるインストゥルメンテーションを追加する
OpenTelemetryの優れた利点は、アプリケーションコードへの最小限の変更で、より多くのインストゥルメンテーションを追加できることです。
例えば、HTTPとExpressJSのトレーシングを追加するには、OpenTelemetry構成に次のインストゥルメンテーションを追加します。これらのインストゥルメンテーションは、完全なリクエスト・レスポンスライフサイクルのスパンを追加します。これらのスパンは、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(),
],
})
利用可能なインストゥルメンテーションの完全なリストは、OpenTelemetry Registryをご覧ください。
リソース属性をカスタマイズする
リソース属性をアプリケーションに合わせてより具体的に変更することで、アプリケーションのトレースがどのようにグループ化されるかを調整できます
const provider = new NodeTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'weblog',
[SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
}),
})
一般的なリソース属性を標準化する取り組みが進行中です。可能な限り、標準属性名に従うことをお勧めします。
パフォーマンスへの影響を軽減する
アプリケーションが多数のスパンをコレクターに送信する場合、これはパフォーマンスに大きな影響を与える可能性があります。この影響を軽減するには、次のアプローチを使用できます
BatchSpanProcessor
を使用してトレースをバッチで送信する
本番環境では、OpenTelemetryのBatchSpanProcessor
を使用して、スパンを1つずつではなくバッチでコレクターに送信できます。ただし、開発およびテスト中は、スパンをバッチで送信したくない場合があります。この状況では、SimpleSpanProcessor
の使用を推奨します。
環境に応じて、トレーシング構成を適切なスパンプロセッサを使用するように設定できます。
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))
}
サンプリングを使用してコレクターに送信するスパンの数を減らす
パフォーマンスへの影響を軽減するもう1つの方法は、確率サンプリングを使用してコレクターに送信するスパンの数を減らすことです。これにより、トレーシングの収集コストは削減されますが、アプリケーションで何が起こっているかを適切に表現できます。
実装例は次のとおりです
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',
}),
})
トレーシングのトラブルシューティング
トレースが表示されない
トレーシングのセットアップ順序は重要です。アプリケーションでは、計測される依存関係をインポートする前に、トレーシングとインストゥルメンテーションを登録するようにしてください。例えば
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'