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

OpenTelemetryトレーシング

トレーシングは、Prisma Clientが実行するアクティビティの詳細なログを、各クエリの実行時間を含めて操作レベルで提供します。これは、アプリケーションのパフォーマンスを分析し、ボトルネックを特定するのに役立ちます。トレーシングはOpenTelemetryに完全に準拠しているため、エンドツーエンドのアプリケーショントレーシングシステムの一部として使用できます。

情報

トレーシングは、Prisma ORMプロジェクトに関する非常に詳細な操作レベルの洞察を提供します。クエリ数、接続数、クエリ総実行時間などの集計された数値レポートが必要な場合は、メトリクスを参照してください。

トレーシングについて

トレーシングを有効にすると、Prisma Clientは以下を出力します。

  • Prisma Clientが行う各操作(例:findMany)に対して1つのトレース。
  • 各トレースには、1つ以上のスパンが含まれます。各スパンは、シリアライズやデータベースクエリなど、操作の1つの段階にかかる時間を表します。スパンはツリー構造で表現され、子スパンは、実行がより大きな親スパン内で発生していることを示します。

トレース内のスパンの数とタイプは、トレースが対象とする操作のタイプによって異なりますが、例は次のとおりです。

image

トレーシングの出力をコンソールに送信したり、JaegerHoneycombDatadogなどの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:データベースクエリの結果を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

考慮事項と前提条件

アプリケーションが多数のスパンをコレクターに送信する場合、パフォーマンスに大きな影響を与える可能性があります。この影響を最小限に抑える方法については、パフォーマンスへの影響を軽減するを参照してください。

トレーシングを使用するには、次の操作を行う必要があります。

  1. 適切な依存関係をインストールする.
  2. OpenTelemetryパッケージをインストールする.
  3. アプリケーションにトレーシングを登録する.

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でプレビュー機能として追加されました。4.2.0から6.1.0までのPrisma ORMのバージョンでは、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つの例を示しています。

  1. @opentelemetry/sdk-trace-node(既存の例)を使用すると、トレーシングのセットアップをきめ細かく制御できます。
  2. @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 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-baseConsoleSpanExporterを使用して、出力トレーシングをコンソールに送信します。

// 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ステートメントでmiddlewaretrueに設定します。

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

これにより、次のスパンタイプがトレースに追加されます。

  • prisma:client:middlewareミドルウェアで操作が費やした時間を表します。

インタラクティブトランザクションをトレースする

インタラクティブトランザクションを実行すると、標準スパンに加えて、次のスパンが表示されます。

  • prisma:client:transactionprismaスパンをラップするルートスパン
    • prisma:engine:itx_runnerクエリエンジンでのインタラクティブトランザクションにかかる時間を表します。
    • prisma:engine:itx_query_builder:インタラクティブトランザクションの構築にかかる時間を表します。

例として、次のPrismaスキーマを見てください。

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

インストルメンテーションを追加する

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レジストリをご覧ください。

リソース属性をカスタマイズする

アプリケーションのトレースをグループ化する方法を調整するには、リソース属性をアプリケーションに固有のものに変更します。

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'