Kysely
Trace Kysely queries, streams and transactions and export pg pool gauges with @unhandled/instrumentation-kysely.
@unhandled/instrumentation-kysely wraps a Kysely dialect so every query, stream and transaction becomes a CLIENT span in queries. It also exports connection pool gauges for pg. It supports Kysely ^0.28.16 and ^0.29.6 with PostgresDialect (pg) and PostgresJSDialect (kysely-postgres-js).
The package is manual only. It has no /integration entry, so the AdonisJS agent never enables it for you.
Install
npm install @unhandled/instrumentation-kyselyWrap the dialect
Create the dialect after OpenTelemetry has started and pass the wrapped dialect to Kysely:
import { Kysely, PostgresDialect } from 'kysely'
import pg from 'pg'
import { observeDialect } from '@unhandled/instrumentation-kysely'
import type { ShopDatabase } from '#types/db'
const connection = { host: 'db.internal', port: 5432, database: 'shop' }
export const pool = new pg.Pool({ ...connection, user: 'shop', max: 10 })
export const db = new Kysely<ShopDatabase>({
dialect: observeDialect({
dialect: new PostgresDialect({ pool }),
dbSystem: 'postgresql',
connection,
}),
})Options
| Option | Default | Effect |
|---|---|---|
dialect | required | The dialect to wrap. |
dbSystem | required | Value of db.system.name, such as postgresql. |
recordQueryText | true | false leaves db.query.text off every span. |
recordErrors | true | false keeps ERROR status but drops the status message and the exception event. |
requireParentSpan | false | Only create spans when a span is already active. |
connection | none | { host, port, database }, reported as server.address, server.port and db.namespace. |
Query parameters are never recorded, but literals inlined in SQL are, and error messages can echo data. Turn off recordQueryText and recordErrors if that matters. requireParentSpan keeps queries that run outside requests and jobs from showing up as background spans.
What it records
Every span is CLIENT kind and named <OPERATION> <table>, such as SELECT orders, read from the SQL text. Without a table the name is the operation alone; without a parsable operation it is the dbSystem value.
- A transaction produces a
BEGINspan, one span per query, thenCOMMITorROLLBACK. - A stream is one span that lasts until the stream is drained, closed or fails.
| Attribute | Value |
|---|---|
db.system.name | dbSystem |
db.query.text | SQL with placeholders, when recordQueryText is on |
db.operation.name | Upper-cased first keyword, such as SELECT |
db.collection.name | Target table, when it can be parsed |
db.namespace | connection.database |
server.address | connection.host |
server.port | connection.port |
db.transaction | true on BEGIN, COMMIT, ROLLBACK and queries inside a transaction |
error.type | Error class name on failure |
The SQL parser is exported as parseSql(sql), returning { operation, collection }.
Pool metrics
registerPgPoolMetrics() reports four gauges for a pg.Pool. Call it after the OpenTelemetry metrics provider is set up, and call the returned function before pool.end().
import { registerPgPoolMetrics } from '@unhandled/instrumentation-kysely'
import { pool } from '#services/db'
const stopPoolMetrics = registerPgPoolMetrics({ pool, max: 10, poolName: 'primary' })
process.once('SIGTERM', async () => {
stopPoolMetrics()
await pool.end()
})| Gauge | Meaning |
|---|---|
db.client.connection.used | Connections checked out |
db.client.connection.idle | Open connections sitting idle |
db.client.connection.pending_requests | Callers waiting for a connection |
db.client.connection.max | The max you passed |
Each point carries db.client.connection.pool.name set to poolName. The names are exported as POOL_METRIC_NAMES. Anything other than a pg.Pool throws a TypeError; postgres.js exposes no pool counters. The gauges show up in metrics.
Duplicate spans
If you also enable OpenTelemetry's pg instrumentation, every query is traced twice, once here and once at the driver. Keep one of them. postgres.js has no OpenTelemetry instrumentation of its own, so wrapping its dialect here never duplicates spans.