A OpenTelemetry instrumentation library for the Postgres.js that provides observability for PostgreSQL database operations.
This instrumentation captures:
- Query Duration: Histogram of query execution times
- Query Count: Total number of queries executed
- Error Count: Number of failed queries
- Connection Count: Number of database connections established
- Connection Duration: How long connections remain active
- Operation Type: SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP
- Table Names: Extracted from SQL queries
- Query Complexity: Low/Medium/High based on query structure
- Query Characteristics: Presence of WHERE, JOIN, ORDER BY, LIMIT clauses
- Parameter Count: Number of query parameters used
- Query Duration Distribution: Histogram with configurable buckets
- Slow Query Detection: Built-in support for identifying performance issues
- Connection Pool Monitoring: Track connection lifecycle events
npm install otel-instrumentation-postgresimport { NodeSDK } from "@opentelemetry/sdk-node";
import { ConsoleSpanExporter } from "@opentelemetry/sdk-trace-node";
import { ConsoleMetricExporter, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { PostgresInstrumentation } from "otel-instrumentation-postgres";
const sdk = new NodeSDK({
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
}),
instrumentations: [
new PostgresInstrumentation({
serviceName: "my-app",
collectQueryParameters: true
}),
],
});
sdk.start();import postgres from "postgres";
import { createOTELEmitter } from "otel-instrumentation-postgres";
// Create your postgres client
const sql = postgres(process.env.DATABASE_URL);
// Wrap it with telemetry
const instrumentedSql = createOTELEmitter(sql);
// Use the instrumented client - all queries are now tracked
const users = await instrumentedSql`SELECT * FROM users WHERE active = ${true}`;| Option | Type | Default | Description |
|---|---|---|---|
serviceName |
string |
- | Service name for telemetry attributes |
enableHistogram |
boolean |
true |
Enable query duration histogram metrics |
histogramBuckets |
number[] |
[0.001, 0.01, 0.1, 0.5, 1, 2, 5, 10, 30, 60, 120, 300, 600] |
Duration buckets in seconds |
collectQueryParameters |
boolean |
false |
Include query parameters in spans |
serverAddress |
string |
process.env.PGHOST |
Database server address |
serverPort |
number |
process.env.PGPORT |
Database server port |
databaseName |
string |
process.env.PGDATABASE |
Database name |
parameterSanitizer |
Function |
Built-in sanitizer | Custom parameter sanitization |
beforeSpan |
Function |
- | Hook called before span creation |
afterSpan |
Function |
- | Hook called after span completion |
responseHook |
Function |
- | Hook called with query result |
Each database query generates a span with rich attributes:
db.system.name:"postgresql"db.query.text: Sanitized SQL querydb.operation.name: SQL operation (SELECT, INSERT, etc.)db.namespace: Database namedb.collection.name: Extracted table namenet.peer.name: Database server addressnet.peer.port: Database server portexception.type: Error type for failed queries
db.parameter_count: Number of query parametersdb.duration_ms: Query duration in millisecondsdb.duration_seconds: Query duration in secondsdb.query.has_where: Whether query has WHERE clausedb.query.has_join: Whether query has JOIN clausedb.query.has_order_by: Whether query has ORDER BY clausedb.query.has_limit: Whether query has LIMIT clausedb.query.complexity: Estimated query complexity (low/medium/high)db.query.type: Query type (read/write/schema/unknown)db.result.row_count: Number of rows returned (for arrays)
db.query.parameter.0,db.query.parameter.1, etc.: Individual query parameters (sanitized)
db.client.operations.duration: Histogram of query durationsdb.client.requests: Counter of total queriesdb.client.errors: Counter of failed queriesdb.client.connections: Counter of database connectionsdb.client.connections.duration: Histogram of connection durations
const instrumentation = new PostgresInstrumentation({
parameterSanitizer: (param, index) => {
// Redact sensitive data
if (typeof param === "string") {
if (param.match(/^\d{4}-\d{4}-\d{4}-\d{4}$/)) {
return "****-****-****-" + param.slice(-4); // Credit card
}
if (param.includes("@")) {
return "[EMAIL]"; // Email addresses
}
}
// Truncate long values
const str = String(param);
return str.length > 50 ? str.substring(0, 50) + "..." : str;
},
});const instrumentation = new PostgresInstrumentation({
serviceName: "user-service",
collectQueryParameters: true,
// Add custom attributes before span creation
beforeSpan: (span, event) => {
span.setAttribute("user.id", getCurrentUserId());
span.setAttribute("request.id", getRequestId());
},
// Monitor slow queries
afterSpan: (span, event) => {
if (event.durationMs > 1000) {
console.warn(`Slow query detected: ${event.sql} (${event.durationMs}ms)`);
}
},
// Analyze query results
responseHook: (span, result) => {
if (Array.isArray(result)) {
span.setAttribute("db.result.count", result.length);
if (result.length === 0) {
span.setAttribute("db.result.empty", true);
}
}
},
});The library automatically analyzes your SQL queries:
// This query will generate:
// - operation: "SELECT"
// - table: "users"
// - has_where: true
// - has_order_by: true
// - has_limit: true
// - complexity: "medium"
// - type: "read"
const users = await instrumentedSql`
SELECT * FROM users
WHERE active = ${true}
ORDER BY created_at DESC
LIMIT 10
`;
// This query will generate:
// - operation: "INSERT"
// - table: "users"
// - has_where: false
// - complexity: "low"
// - type: "write"
await instrumentedSql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
`;- Node.js 18+
- npm
# Clone the repository
git clone https://github.com/wataruoguchi/otel-instrumentation-postgres.git
cd otel-instrumentation-postgres
# Install dependencies
cd lib && npm install
cd ../example && npm install
# Run tests
cd ../lib && npm test
# Build the package
npm run buildThis project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- Postgres.js - The excellent PostgreSQL client
- OpenTelemetry - The observability framework