Drizzle ORM
Trace every Drizzle ORM query in silgi analytics — zero config, automatic SQL capture.
Every database query is a potential bottleneck. The Drizzle integration wraps your Drizzle instance so every select, insert, update, and delete appears as a span in silgi analytics — with duration, SQL text, table name, and operation type. No manual instrumentation needed.
Setup
Install both packages:
bash pnpm add silgi drizzle-orm bash npm install silgi drizzle-orm bash bun add silgi drizzle-orm Wrap your Drizzle instance with instrumentDrizzle, then use withSilgiCtx inside procedures to connect queries to the current request trace:
import { } from 'silgi'
import { , } from 'silgi/drizzle'
import { } from 'drizzle-orm/node-postgres'
import * as from './schema'
const = ((..!, { }), { : 'myapp' })
const = ()
const = .(async ({ }) => {
return (, () => .select().from(.users))
})
export default { : .(.({ : { : } })) }What gets captured
Each query produces a span with these attributes:
| Attribute | Type | Description |
|---|---|---|
db.system | string | Database system identifier (default: postgresql) |
db.name | string | Logical database name (e.g. auth, ecommerce) |
db.operation | string | SQL operation (SELECT, INSERT, UPDATE, DELETE) |
db.statement | string | Full SQL text (truncated to maxQueryTextLength) |
db.transaction | boolean | true when the query runs inside a transaction |
net.peer.name | string | Database host |
net.peer.port | number | Database port |
Span naming
Spans are named using the pattern db.{operation}.{table}:
db.select.user — SELECT from user table
db.insert.order — INSERT into order table
db.update.product — UPDATE on product table
db.delete.session — DELETE from session table
db.tx.select.user — SELECT inside a transaction
db.tx.insert.order_item — INSERT inside a transactionThis is more useful than generic names like db.query — you can immediately see which table and operation is slow in your analytics dashboard.
Configuration
All options are optional. Pass them as the second argument to instrumentDrizzle:
| Name | Type | Default | Description |
|---|---|---|---|
dbName | string | undefined | Logical database name. Appears as db.name attribute |
dbSystem | string | 'postgresql' | Database system identifier |
captureQueryText | boolean | true | Include SQL text in spans |
maxQueryTextLength | number | 1000 | Truncate SQL text after this many characters |
peerName | string | undefined | Database host. Appears as net.peer.name |
peerPort | number | undefined | Database port. Appears as net.peer.port |
Multiple databases
Instrument each database separately with a distinct dbName:
import { } from 'silgi/drizzle'
import { } from 'drizzle-orm/node-postgres'
const = ((..!), {
: 'auth',
: 'db.example.com',
: 5432,
})
const = ((..!), {
: 'ecommerce',
: 'db.example.com',
: 5432,
})In your analytics, spans from each database are distinguished by the db.name attribute.
Transactions
Transaction queries are automatically detected. They get a db.tx. prefix in the span name and the db.transaction: true attribute:
import { } from 'silgi/drizzle'
const = s.$resolve(async ({ , }) => {
return (, () =>
db.transaction(async () => {
// span: db.tx.insert.order
const [] = await .insert(schema.orders).values().returning()
// span: db.tx.insert.order_item
await .insert(schema.orderItems).values(.items.map(() => ({ ..., : .id })))
return
}),
)
})Security
By default, the full SQL text is captured in spans. If your queries contain sensitive data (PII, tokens, secrets), disable SQL capture in production:
import { } from 'silgi/drizzle'
import { } from 'drizzle-orm/node-postgres'
const = ((..!), {
: 'myapp',
: .. !== 'production',
})You still get span names, durations, and operation types — just not the raw SQL.
withSilgiCtx is required. Without it, queries execute normally but are invisible to silgi analytics. Always wrap your database calls inside withSilgiCtx(ctx, () => ...) in your procedures.