@kubiks/otel-drizzle provides comprehensive OpenTelemetry instrumentation for Drizzle ORM. Add distributed tracing to your database queries with a single line of code—supports PostgreSQL, MySQL, and SQLite.
Visualize your database queries with detailed span information including operation type, SQL statements, and performance metrics.
Use instrumentDrizzleClient() to add tracing to your Drizzle database instance:
Copy
import { drizzle } from "drizzle-orm/postgres-js";import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";// Create your Drizzle database instance as usualconst db = drizzle(process.env.DATABASE_URL!);// Add instrumentation with a single lineinstrumentDrizzleClient(db);// That's it! All queries are now traced automaticallyconst users = await db.select().from(usersTable);
This is the simplest and most straightforward approach—just wrap your existing Drizzle instance!
import { drizzle } from "drizzle-orm/postgres-js";import postgres from "postgres";import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";// Using connection string directlyconst db = drizzle(process.env.DATABASE_URL!);instrumentDrizzleClient(db, { dbSystem: "postgresql" });// Or with a client instanceconst queryClient = postgres(process.env.DATABASE_URL!);const db = drizzle({ client: queryClient });instrumentDrizzleClient(db, { dbSystem: "postgresql", dbName: "myapp", peerName: "db.example.com", peerPort: 5432,});
node-postgres (pg)
Copy
import { drizzle } from "drizzle-orm/node-postgres";import { Pool } from "pg";import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";// Using connection string directlyconst db = drizzle(process.env.DATABASE_URL!);instrumentDrizzleClient(db, { dbSystem: "postgresql" });// Or with a pool instanceconst pool = new Pool({ connectionString: process.env.DATABASE_URL});const db = drizzle({ client: pool });instrumentDrizzleClient(db, { dbSystem: "postgresql" });
Copy
import { drizzle } from "drizzle-orm/mysql2";import mysql from "mysql2/promise";import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";// Using connection string directlyconst db = drizzle(process.env.DATABASE_URL!);instrumentDrizzleClient(db, { dbSystem: "mysql" });// Or with a connection instanceconst connection = await mysql.createConnection({ host: "localhost", user: "root", database: "mydb", // ... other connection options});const db = drizzle({ client: connection });instrumentDrizzleClient(db, { dbSystem: "mysql", dbName: "mydb", peerName: "localhost", peerPort: 3306,});
better-sqlite3
Copy
import { drizzle } from "drizzle-orm/better-sqlite3";import Database from "better-sqlite3";import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";// Using file path directlyconst db = drizzle("sqlite.db");instrumentDrizzleClient(db, { dbSystem: "sqlite" });// Or with a Database instanceconst sqlite = new Database("sqlite.db");const db = drizzle({ client: sqlite });instrumentDrizzleClient(db, { dbSystem: "sqlite" });
LibSQL/Turso
Copy
import { drizzle } from "drizzle-orm/libsql";import { createClient } from "@libsql/client";import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";// Using connection config directlyconst db = drizzle({ connection: { url: process.env.DATABASE_URL!, authToken: process.env.DATABASE_AUTH_TOKEN, }});instrumentDrizzleClient(db, { dbSystem: "sqlite" });// Or with a client instanceconst client = createClient({ url: process.env.DATABASE_URL!, authToken: process.env.DATABASE_AUTH_TOKEN,});const db = drizzle({ client });instrumentDrizzleClient(db, { dbSystem: "sqlite" });
import { db } from "@/lib/db";import { users } from "@/db/schema";// RLS queries are automatically tracedawait db.transaction(async (tx) => { // SET LOCAL role is traced await tx.execute(sql`SET LOCAL role = 'authenticated'`); await tx.execute(sql`SELECT set_config('request.jwt.claim.sub', '${userId}', true)`); // Regular queries with RLS applied const userPosts = await tx .select() .from(posts) .where(eq(posts.userId, userId));});
import { Pool } from "pg";import { drizzle } from "drizzle-orm/node-postgres";const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, // Maximum pool size idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000,});export const db = drizzle({ client: pool });instrumentDrizzleClient(db);
Configure SQL Capture Wisely
Disable SQL capture in production if queries contain sensitive data:
The instrumentation adds minimal overhead (~1-2ms per query) for tracing operations.
Sampling
Use OpenTelemetry sampling to reduce data volume in high-traffic applications:
Copy
import { TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";const sdk = new NodeSDK({ sampler: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces});
Batch Span Processing
Use batch span processors for better performance:
Copy
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";const sdk = new NodeSDK({ spanProcessor: new BatchSpanProcessor(exporter),});
Ensure OpenTelemetry is initialized before instrumenting Drizzle:
Copy
import { NodeSDK } from "@opentelemetry/sdk-node";const sdk = new NodeSDK({ // ... configuration});sdk.start();// Then instrument Drizzleimport { db } from "@/lib/db";instrumentDrizzleClient(db);
Make sure you’re using Drizzle’s transaction API correctly:
Copy
// Correct: All queries in callback are groupedawait db.transaction(async (tx) => { await tx.insert(users).values(data);});// Incorrect: Manual transaction control not supportedawait db.execute(sql`BEGIN`);await db.insert(users).values(data);await db.execute(sql`COMMIT`);