Silgi

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:

server.ts
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:

AttributeTypeDescription
db.systemstringDatabase system identifier (default: postgresql)
db.namestringLogical database name (e.g. auth, ecommerce)
db.operationstringSQL operation (SELECT, INSERT, UPDATE, DELETE)
db.statementstringFull SQL text (truncated to maxQueryTextLength)
db.transactionbooleantrue when the query runs inside a transaction
net.peer.namestringDatabase host
net.peer.portnumberDatabase 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 transaction

This 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:

NameTypeDefaultDescription
dbNamestringundefinedLogical database name. Appears as db.name attribute
dbSystemstring'postgresql'Database system identifier
captureQueryTextbooleantrueInclude SQL text in spans
maxQueryTextLengthnumber1000Truncate SQL text after this many characters
peerNamestringundefinedDatabase host. Appears as net.peer.name
peerPortnumberundefinedDatabase port. Appears as net.peer.port

Multiple databases

Instrument each database separately with a distinct dbName:

db.ts
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:

server.ts
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.

On this page