The Postgres integration automatically instruments the pg (node-postgres) library to capture database query performance and errors.
Installation
The integration is enabled by default in Node.js:
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'your-dsn',
// postgresIntegration is included by default
});
Basic Usage
Just use the pg library normally:
import { Client } from 'pg';
const client = new Client({
host: 'localhost',
port: 5432,
database: 'mydb',
user: 'user',
password: 'password',
});
await client.connect();
// Automatically tracked
const result = await client.query('SELECT * FROM users WHERE id = $1', [123]);
await client.end();
Configuration
Default Configuration
Sentry.init({
dsn: 'your-dsn',
integrations: [
Sentry.postgresIntegration({
ignoreConnectSpans: false, // Track connection spans
}),
],
});
Ignore Connection Spans
If you want to track only queries and not connections:
Sentry.init({
dsn: 'your-dsn',
integrations: [
Sentry.postgresIntegration({
ignoreConnectSpans: true, // Don't track connections
}),
],
});
Integration Options
Whether to ignore database connection spans
What Gets Captured
The integration captures:
Query Operations
- SQL query text
- Query parameters
- Execution time
- Row counts
- Database name
- Connection details
Span Attributes
{
'db.system': 'postgresql',
'db.name': 'mydb',
'db.statement': 'SELECT * FROM users WHERE id = $1',
'db.operation': 'SELECT',
'server.address': 'localhost',
'server.port': 5432,
}
Supported Operations
Simple Queries
const result = await client.query('SELECT * FROM users');
// Span: SELECT
Parameterized Queries
const result = await client.query(
'SELECT * FROM users WHERE email = $1',
['[email protected]']
);
// Span: SELECT (parameters captured)
Prepared Statements
const result = await client.query({
name: 'fetch-user',
text: 'SELECT * FROM users WHERE id = $1',
values: [123],
});
// Span: SELECT with statement name
Transactions
await client.query('BEGIN');
try {
await client.query('INSERT INTO users (name) VALUES ($1)', ['John']);
await client.query('INSERT INTO profiles (user_id) VALUES ($1)', [1]);
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
}
// Each query creates a separate span
Practical Examples
Basic CRUD Operations
import * as Sentry from '@sentry/node';
import { Client } from 'pg';
const client = new Client({
connectionString: process.env.DATABASE_URL,
});
await client.connect();
// Create
const insertResult = await client.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
['John Doe', '[email protected]']
);
// Span: INSERT
// Read
const selectResult = await client.query(
'SELECT * FROM users WHERE email = $1',
['[email protected]']
);
// Span: SELECT
// Update
const updateResult = await client.query(
'UPDATE users SET name = $1 WHERE id = $2',
['Jane Doe', 123]
);
// Span: UPDATE
// Delete
const deleteResult = await client.query(
'DELETE FROM users WHERE id = $1',
[123]
);
// Span: DELETE
await client.end();
Connection Pool
import { Pool } from 'pg';
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'mydb',
max: 20,
idleTimeoutMillis: 30000,
});
// Automatically tracked
const result = await pool.query('SELECT NOW()');
await pool.end();
Complex Queries
// Join query
const result = await client.query(`
SELECT u.id, u.name, p.bio
FROM users u
LEFT JOIN profiles p ON p.user_id = u.id
WHERE u.active = true
ORDER BY u.created_at DESC
LIMIT 10
`);
// Full query text captured
// Aggregation
const stats = await client.query(`
SELECT
DATE(created_at) as date,
COUNT(*) as count,
AVG(price) as avg_price
FROM orders
WHERE created_at >= $1
GROUP BY DATE(created_at)
ORDER BY date DESC
`, [new Date('2024-01-01')]);
// Query with aggregations tracked
Async/Await with Error Handling
async function getUser(userId) {
return await Sentry.startSpan(
{ name: 'Get User', op: 'db.query' },
async () => {
try {
const result = await client.query(
'SELECT * FROM users WHERE id = $1',
[userId]
);
if (result.rows.length === 0) {
throw new Error('User not found');
}
return result.rows[0];
} catch (error) {
Sentry.captureException(error, {
contexts: {
database: {
query: 'SELECT user',
user_id: userId,
},
},
});
throw error;
}
}
);
}
import { Cursor } from 'pg-cursor';
const cursor = client.query(
new Cursor('SELECT * FROM large_table WHERE active = true')
);
// Read in batches
const read = (rows, done) => {
console.log(rows);
if (rows.length === 100) {
cursor.read(100, read);
} else {
cursor.close(done);
}
};
cursor.read(100, read);
// Each read operation tracked
Transaction with Savepoints
await client.query('BEGIN');
try {
await client.query('INSERT INTO users (name) VALUES ($1)', ['John']);
await client.query('SAVEPOINT before_profile');
try {
await client.query('INSERT INTO profiles (user_id) VALUES ($1)', [1]);
} catch (profileError) {
await client.query('ROLLBACK TO SAVEPOINT before_profile');
}
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
}
View database performance in Sentry:
Transaction: POST /api/users
├─ db.query (PostgreSQL)
│ ├─ SELECT * FROM users
│ └─ Duration: 45ms
├─ db.query (PostgreSQL)
│ ├─ INSERT INTO users
│ └─ Duration: 23ms
└─ Total: 150ms
Metrics Tracked
- Query Duration: Time taken for each query
- Query Count: Number of queries per transaction
- Slow Queries: Identify performance bottlenecks
- Error Rates: Track failed queries
Query Parameterization
Query parameters are captured in spans. Be careful with sensitive data.
Filtering Sensitive Data
Sentry.init({
dsn: 'your-dsn',
beforeSendSpan(span) {
// Remove query parameters from database spans
if (span.op === 'db.query') {
if (span.description?.includes('password')) {
span.description = 'SELECT [filtered]';
}
}
return span;
},
});
Source Code
The Postgres integration is implemented in:
packages/node/src/integrations/tracing/postgres.ts:24
Best Practices
Use connection pooling for better performance and resource management.
1. Use Connection Pools
import { Pool } from 'pg';
const pool = new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
2. Handle Errors Properly
try {
const result = await client.query('SELECT * FROM users');
} catch (error) {
Sentry.captureException(error);
// Handle error
}
3. Use Prepared Statements
// Reusable prepared statement
const query = {
name: 'fetch-user',
text: 'SELECT * FROM users WHERE id = $1',
};
const user1 = await client.query(query, [1]);
const user2 = await client.query(query, [2]);
// Same prepared statement reused
4. Monitor Connection Pool
pool.on('connect', () => {
console.log('New client connected');
});
pool.on('error', (err) => {
Sentry.captureException(err);
});
Troubleshooting
Spans Not Appearing
Ensure tracing is enabled:
Sentry.init({
dsn: 'your-dsn',
tracesSampleRate: 1.0,
});
Missing Query Text
Query text should be automatically captured. If missing, check:
// Ensure pg version is supported
// The integration requires pg >= 8.0.0
Too Many Connection Spans
Disable connection tracking:
Sentry.postgresIntegration({
ignoreConnectSpans: true,
});