pgbadger
Log analysis for slow query discovery on PostgreSQL 18.4 - turn log_min_duration_statement output into ranked reports that feed EXPLAIN review and index design tickets.
Search across all documentation pages
Log analysis for slow query discovery on PostgreSQL 18.4 - turn log_min_duration_statement output into ranked reports that feed EXPLAIN review and index design tickets.
ALTER SYSTEM SET log_destination = 'csvlog';
ALTER SYSTEM SET logging_collector = on;
ALTER SYSTEM SET log_min_duration_statement = '250ms';
ALTER SYSTEM SET log_statement = 'none';
ALTER SYSTEM SET log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h ';
SELECT pg_reload_conf();pgbadger /var/log/postgresql/postgresql-18-main.csv \
-o /var/reports/pgbadger/$(date +%F) \
--incremental \
--exclude-query="^(BEGIN|COMMIT|SET|SHOW)"When to reach for this:
log_statement = allStep 1 - Collect one day of logs
sudo ls -la /var/log/postgresql/
# postgresql-18-main.csv, postgresql-18-main.csv.1, ...Step 2 - Generate report
OUT=/var/reports/pgbadger/2026-07-09
mkdir -p "$OUT"
pgbadger /var/log/postgresql/postgresql-18-main.csv* \
-o "$OUT" \
--jobs=4 \
--top=50Step 3 - Triage top query
-- Copy normalized query from report into psql
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT o.id, o.total
FROM orders o
WHERE o.tenant_id = $1 AND o.status = 'open'
ORDER BY o.created_at DESC
LIMIT 50;What this demonstrates:
app=%a when log_line_prefix includes %a| Setting | Recommended | Avoid |
|---|---|---|
log_min_duration_statement | 200-500ms prod | 0 (logs everything) |
log_duration | off when using min_duration | duplicate noise |
log_lock_waits | on | missing lock stories |
log_checkpoints | on for IO triage | - |
pgbadger --incremental --report-per-database \
--outdir /var/reports/pgbadger/incr \
/var/log/postgresql/postgresql-18-main.csvgrep "duration:" /var/log/postgresql/postgresql-18-main.csv \
| awk -F'duration: ' '{print $2}' | sort -rn | head -20jsonlog without conversion. Fix: use csvlog or verify pgbadger version supports your format.log_min_duration_statement.log_parameter_max_length=0 or redact reports.%t vs UTC. Fix: align log_timezone with incident tooling.pg_stat_statements mean vs max time.| Alternative | Use When | Don't Use When |
|---|---|---|
pg_stat_statements | Live workload aggregates | Need point-in-time before reset |
| pganalyze | Budget for SaaS plan history | Air-gapped or cost-sensitive |
auto_explain | Sample plans automatically | Full workload log volume |
Daily incremental cron for production; weekly review meeting walks top 10 queries. After each release, run ad hoc report for 24h window.
Yes if you export CloudWatch or file logs to S3 and download CSV. Many teams use pg_stat_statements on RDS plus manual EXPLAIN instead.
Start 500ms OLTP, 2s analytics replica. Lower when storage is cheap and query volume is moderate. Raise during incidents if log IO is hot.
Stack versions: This page was written for PostgreSQL 18.4 and pgBadger 12.4+.
Reviewed by Chris St. John·Last updated Jul 19, 2026