简介:pgBadger是一款用纯Perl语言编写的PostgreSQL日志分析器,以解析速度见长,能够自动识别syslog、stderr、csvlog等常见日志格式,并借助JavaScript图表库生成可视化报告,适合DBA、运维工程师及需要深度排查数据库性能的开发者在日常巡检和故障分析中使用。资源为pgBadger 11.5的开源发行版,共54个文件、约2.2MB,除核心Perl脚本外,还包含jQuery、jqplot等前端图表资源、测试脚本、文档与许可证说明,整体结构紧凑,便于直接部署或二次开发。目前已有440人学习查看。压缩包内附有完整源码、构建配置(如Makefile.PL与MANIFEST)、单元测试样例及详细文档,读者既可快速上手生成日志分析报表,也能参考其内部实现扩展自定义功能;同时自带对gzip压缩日志的支持,有助于在日志量大、存储受限的环境下高效完成分析任务。
1. pgBadger 是什么:一份 PostgreSQL 日志能榨出多少信息
有一次线上慢查询排查,手里摆着 2GB 的 PostgreSQL 日志,我 grep 了大半个下午才凑出几条 5 秒以上的语句,那滋味不想再试第二次。pgBadger 就是一个为提高速度而构建的 PostgreSQL 日志分析器:Perl 写的命令行工具,喂给它 PostgreSQL 的文本日志,它吐出一份可交互的 HTML 报告,慢查询 Top、锁等待、Checkpoint、临时文件、全表扫描在这些区块里一次看全。它不装数据库插件、不写回数据库、不需要改业务代码,唯一的前提是你把 PostgreSQL 的日志开关按格式打开。适合经常要处理慢查询的 DBA、后端负责人和 SRE:日志越乱越有价值,这份工具就是把“出了什么问题”从黑匣子里翻出来的那双手。它是开源项目,系统包里一条命令就能装,后面我会把配置、命令和踩过的坑一起展开。
2. 先搞清它分析什么:pgBadger 的输入、报告与提速原理
2.1 它吃的是原生日志,不是 pg_stat_statements
上手 pgBadger 之前,必须先分清两个概念。pg_stat_statements 是 PostgreSQL 内置插件,长期累计每类 SQL 的总耗时、执行次数,适合看趋势;而 pgBadger 解析的是数据库进程写出来的原始日志文件,回答的是“这段时间里到底发生了什么”。
常见误解是:我已经开了 pg_stat_statements,还需要日志分析器吗?需要。pg_stat_statements 只记录执行过的语句聚合,不记录锁等待过程、checkpoint 行为、临时文件写入、连接断开这类旁路信息,而这些往往是数据库抖动的大头。还有一层容易忽视的区别:MySQL 的慢日志有固定表结构、自带查询接口,把这个习惯带过来会发现 PostgreSQL 完全不同——它的默认日志是纯文本,没有内置慢日志表,最多靠 log_line_prefix 控制格式。这就是 pgBadger 这类开源项目存在的意义:把一条条裸日志变成可读的指标。
能让 pgBadger 出内容的日志开关大致有五个,我一般这样开:log_min_duration_statement 记录超过阈值的慢语句,log_checkpoints 记录检查点事件,log_lock_waits 记录锁等待,log_temp_files 记录临时文件,log_autovacuum_min_duration 记录自动 vacuum。每开一个,报告里就多一个区块;开少了报告就瘦,开多了文件涨得快,第 3 章会给出完整配置。
2.2 原生日志里有什么:一行日志能被拆出哪些字段
pgBadger 的解析逻辑,本质上就是把你日志里每个字段映射到报告维度。拿一条真实的日志为例:
2024-06-01 10:00:00.123 CST [23456]: [123-1] user=dba,db=app,app=psql,client=127.0.0.1 LOG: duration: 2500.123 ms statement: SELECT * FROM orders WHERE user_id = 123;拆开看这一行值多少钱:开头时间戳给报告时间轴;[23456] 是进程号,用于区分连接;[123-1] 是会话行号,pgBadger 靠它把一条跨行消息拼回同一事件;user、db、app、client 分别是用户、数据库、客户端应用、来源地址,报告按库按用户分组全得靠它们;duration 是慢查询耗时;statement 是语句正文。pgBadger 还会把这条语句里的数字参数替换成 ?,做参数归一化,所以同一条 SQL 的不同参数值会聚合在一起排名,不会因为 where 条件里数字不同就被拆成几百行。
这里面最容易被忽略的是 [123-1] 的会话行号。PostgreSQL 的日志里,一条 ERROR 常常带出 detail 和 context 多行内容,而只有第一行才有前缀里的完整字段;没有序号,多行消息会被当成独立事件,报告里的语句会被拦腰截断。所以 log_line_prefix 里有没有 %l,直接决定报告能不能拼全语句。这也是为什么很多人换了 log_line_prefix 之后报告突然“少了一半数据”的根本原因。
2.3 单遍流式解析:快在哪,和自写脚本的差距
标题里“为提高速度而构建”不是宣传话术,是它的实现方式决定的。pgBadger 处理日志是单遍流式:一行一行读,正则提取出时间、进程号、数据库、用户、语句耗时,在内存里做累加,全程不把日志导入数据库,也不需要二次 ETL。这样做最大的收益是内存可控,处理 10GB 级别的日志时,内存占用通常维持在几百 MB 以内,而不是像导入数据库统计那样先把数据搬一遍。
对比一下常见的替代方案就能看出差别。用 grep 加 awk 自己统计:grep 本身很快,但你要自己处理多行消息、自己按分钟聚合、自己做 Top 排序,写出来能用,维护成本不小;把日志灌进 PostgreSQL 再 SQL 分析:要先清洗格式,再建表导入,日志一多大半时间花在导入而不是分析上。pgBadger 把这几步都省了,一条命令出 HTML,还能直接读 .gz、.bz2、.xz 压缩日志,不用先解压占磁盘。新版本还支持多线程并行解析不同日志文件,适合日志被按天切成很多小文件的场景。
有一点要提醒:它快,不代表它对日志格式不挑。输入格式越规整,解析越快;如果 log_line_prefix 配得乱七八糟,它会先花时间做格式猜测,甚至直接拒绝。所以想享受速度红利,前提是第 3 章的配置一步都不能省。
2.4 开源分发与版本差异:从 GitHub 拿源码还是走系统包
pgBadger 是开源项目,用的 PostgreSQL License,属于宽松类许可证,改完再分发也没有像 GPL 那样强的传染性,公司内部二次开发负担小。你直接搜 GitHub 的 darold/pgbadger 就能看到仓库和 release 包,很多 postgresql 安装教程的末尾也会顺手提它一句。
获取方式取决于你的操作系统。Debian/Ubuntu 的 apt 仓库、RHEL/CentOS 的 EPEL 仓库里基本都有 pgbadger 包,一条命令装完,好处是依赖有人管。但系统仓库里的版本可能落后 GitHub 一个大版本,而新版本往往意味着支持更新的 PostgreSQL 日志格式,比如 PostgreSQL 15 之后新增的 jsonlog 日志格式,老版本 pgBadger 就不认。我的习惯是:生产环境优先用系统包,遇到“日志格式识别不了”这类问题时,再去 GitHub 拉新 release 装源码版。装完先跑一句 pgbadger --version,确认版本再往下走。
3. 从 PostgreSQL 开日志到跑出第一份 HTML:完整配置与命令
3.1 PostgreSQL 日志参数:log_line_prefix 必须按这个格式配
pgBadger 解析依赖两个东西:日志里有内容,格式它能认。PostgreSQL 侧需要改 postgresql.conf 里的这几项,改完执行 SELECT pg_reload_conf(); 即可,不用重启实例:
logging_collector = on log_destination = 'stderr' log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h ' log_min_duration_statement = 1000 log_checkpoints = on log_lock_waits = on log_temp_files = 0 log_autovacuum_min_duration = 0log_line_prefix 是 pgBadger 最看重的格式。%t 是带毫秒的时间戳,%p 是进程号,%l 是会话行号,前面说过它负责把跨行日志拼回同一事件;%u、%d、%a、%h 分别是用户、数据库、应用名、客户端地址,报告里按库按用户分组就靠它们。注意行尾我留了一个空格,这是为了让消息正文与前缀隔开,pgBadger 匹配前缀时会少很多边界问题。
log_min_duration_statement 设 1000 表示只记录执行超过 1 秒的语句;如果你怀疑慢查询被漏了,先往小调,比如 100,代价是日志文件明显变大。log_temp_files 设 0 表示记录所有落盘的临时文件,不设这个值,报告里的 Temp files 区块就是空的。改完在 psql 里验证:
SHOW log_line_prefix; SELECT pg_reload_conf();SHOW 输出的字符串要和配置文件完全一致,如果这里带转义字符或者换行,pgBadger 那边大概率要踩坑。注意 reload 只对新产生的日志生效,已经写出去的旧日志格式不会变,所以改完配置最好等几分钟再跑分析,或者直接把旧日志移走。我在 5.1 里讲的那个翻车案例,就是没等新日志直接排了 cron。
3.2 安装 pgBadger:apt、yum、源码三选一
安装这一步没有难度,三个常见渠道如下:
# Debian / Ubuntu sudo apt install pgbadger -y # RHEL / CentOS 需要先开 EPEL sudo dnf install epel-release -y sudo dnf install pgbadger -y # 从 GitHub release 拿源码包安装 tar xzf pgbadger-*.tar.gz cd pgbadger-* perl Makefile.PL make sudo make installapt 和 dnf 适合图省事的场景,装完直接有 pgbadger 命令。源码安装的好处是版本新,perl Makefile.PL 阶段会提示缺哪些 Perl 模块,常见的有 Text::CSV_XS 这类纯文本解析加速模块,缺什么用 cpan 装上即可。装完验证一下:
pgbadger --version命令能输出版本就说明可执行文件在 PATH 里。如果日志文件是 postgres 用户写的,而你用 root 跑 pgbadger,记得文件要能读、目录要有执行权限,否则后面会遇到 Permission denied。还可以顺手看一眼 pgbadger --help 里的支持列表,确认你本地的版本支持哪些日志格式,尤其是 jsonlog。
3.3 第一条命令与报告验证
最小命令就一行,指定输出文件,再把日志文件或通配符丢进去:
pgbadger -o /tmp/pg_report.html /var/log/postgresql/postgresql-*.log-o 指定 HTML 输出路径。stderr 是最常见的日志格式,pgBadger 会自动识别,所以不用显式写 -f stderr;如果你用的是 csvlog 或 jsonlog,才需要在 -f 参数里指定。日志多、内存少时,可以加 -q 关闭进度条输出。跑完屏幕上会出现一行类似 “Html report written to /tmp/pg_report.html” 的提示,然后用浏览器打开该文件。
这里有个新手常踩的点:日志文件名带不带日期后缀,取决于你部署 PostgreSQL 时是否开了日志轮转,通配符写得不对就会“找不到文件”。先 ls /var/log/postgresql/ 看真实文件名再写命令,比盲猜靠谱。如果命令报 “Wrong log_line_prefix” 或者解析完报告里全是 0,回到 3.1 检查 SHOW log_line_prefix 的输出。第一次打开报告,先看总请求量有没有数字,再翻到 Top Queries 看有没有语句,这两处都有内容才说明日志喂对了。
3.4 新报告先看哪五个区块
第一次打开 HTML 报告,很多人会被满屏图表晃到,我建议只看五个区块,它们信息密度最高:
| 报告区块 | 能看出什么 | 对应日志开关 |
|---|---|---|
| General Statistics | 总请求数、每分钟请求数、平均耗时,先看量级是否异常 | log_min_duration_statement |
| Top Queries | 慢语句排行,按总耗时、平均耗时、最大耗时排序 | log_min_duration_statement |
| Checkpoints | 检查点频率和耗时,看是否写盘压力大 | log_checkpoints |
| Lock Waits | 锁等待次数、等待时间,定位锁竞争 | log_lock_waits |
| Temp Files | 每次查询落盘的临时文件大小,判断 work_mem 够不够 | log_temp_files |
Top Queries 里的语句是做了参数归一化的:同一条 SQL 不同参数值会被聚合成一类,字面量被替换成 ?,所以排名更公平,不会出现同一逻辑的 SQL 因为参数不同被拆成几百行。看的时候先按“总耗时”排,再按“平均耗时”排,前者找总量凶手,后者找单次异常。另外报告里有按数据库、按用户的筛选器,排查某个业务模块的问题时,先把范围缩到一个库再看 Top,比全局列表直观得多。
4. 把 pgBadger 变成日常巡检:时间窗、增量解析与定时任务
4.1 常用参数怎么设:-b、-e、-t、-d 的组合用法
单次跑报告只是开始,要把 pgBadger 用成日常巡检工具,必须学会限定时间窗。下面几个参数是我每次写脚本都要用到的:
| 参数 | 作用 | 我的常用值 |
|---|---|---|
| -b / --begin | 只解析该时间之后的行 | “2024-06-01 00:00:00” |
| -e / --end | 只解析该时间之前的行 | “2024-06-02 00:00:00” |
| -t / --top | Top 查询显示条数 | 30 |
| -d / --dbname | 只看某个数据库 | 业务库名 |
| -s / --size | 最多读取多少日志量 | 视日志大小而定 |
组合起来是这样:
pgbadger -b "2024-06-01 00:00:00" -e "2024-06-02 00:00:00" \ -t 30 -o /tmp/report_day.html /var/log/postgresql/postgresql-*.log-b 和 -e 解析的是日志里的时间戳,不是文件修改时间,所以即使日志文件混着好几天的内容,也能精确切出一天。时间格式要带空格,命令行里必须加引号,这个细节在 cron 里最容易翻车,少一对引号 shell 就把时间拆成了两个参数。如果你只要最近一小时,-b 也可以用 date 命令动态生成,但注意时区和第 5 章那个坑。-s我一般用在探测场景:不确定日志有多大、机器内存紧的时候,先限制只读最后 2GB 跑一份小报告,观察资源消耗再决定要不要全量。
4.2 增量分析:不要让报告一次比一次慢
全量日志只有几 GB 时,多久跑一次无所谓;跑了一个月后还每次从头解析,就是在浪费时间。增量分析的目的很直接:只处理上次跑完之后新产生的日志。
常见做法有两种。第一种是时间窗法,配合日志轮转:每天用 -b 限定昨天零点、-e 限定今天零点,只解析前一天。第二种是偏移记录法,pgBadger 支持记录上次解析到的日志偏移,下次从偏移处继续,参数名在不同版本里略有差别,装好后先跑一下 pgbadger --help 确认你本地版本的写法。我自己的生产环境更常用时间窗法,因为逻辑直白、排障容易;偏移法省解析时间,但日志被轮转删除后会丢失中间段,反而不适合日志保留期短的环境。
# 每天只解析 24 小时内的日志 pgbadger -b "$(date -d 'yesterday 00:00' '+%Y-%m-%d %H:%M:%S')" \ -e "$(date '+%Y-%m-%d %H:%M:%S')" \ -o /var/www/pgbadger/daily.html \ /var/log/postgresql/postgresql-*.log这段脚本用 date 动态生成起止时间,配合 crontab 每天执行,就是一套最朴素的增量日报。注意 date -d 语法在 Linux 和 macOS 上不一样,Linux 用 -d,macOS 要写 -v-1d,服务器场景基本都是 Linux,不会有歧义。切换增量方式或者日志目录变化之后,先跑一次全量报告作为新基线,免得新旧报告口径对不上。
4.3 用 cron 定时生成日报并自动清理旧报告
定时任务我放在 /opt/scripts/pgbadger_daily.sh,内容比单条命令多两件事:输出目录按日期命名、超过 90 天的旧报告自动删除。日志权限记得给执行用户可读权限,我是用一个专门的 postgres 系统账户跑的,避免和业务权限搅在一起。
#!/bin/bash LOG_DIR="/var/log/postgresql" OUT_DIR="/var/www/pgbadger" YESTERDAY=$(date -d 'yesterday' '+%Y-%m-%d') TODAY=$(date '+%Y-%m-%d') pgbadger -b "$YESTERDAY 00:00:00" -e "$TODAY 00:00:00" \ -o "$OUT_DIR/report-$YESTERDAY.html" \ "$LOG_DIR"/postgresql-*.log >/dev/null find "$OUT_DIR" -name 'report-*.html' -mtime +90 -deletecrontab 里加一行:
30 2 * * * /opt/scripts/pgbadger_daily.sh >> /var/log/pgbadger_cron.log 2>&1每天凌晨 2 点半执行,正好避开业务高峰,早上上班打开浏览器就能看到昨天全天的报告。重定向到日志文件是为了排障:脚本里 date、通配符出错时能看到痕迹。跑失败了别只盯着 pgbadger 报错,先看这个日志文件,血泪经验是八成问题出在引号或文件权限,而不是 pgBadger 本身。
4.4 巡检阈值:哪些数字一报警就该处理
报告生成只是第一步,会读才有效果。我按踩过坑的经验给一档初始阈值,你可以根据业务调整:
| 指标 | 注意阈值 | 说明 |
|---|---|---|
| 每分钟请求数 | 较前一日骤降 50% 以上 | 连接可能被锁或连接池耗尽 |
| 慢查询最大耗时 | 超过 5 秒 | 单语句异常,重点看 Top Queries |
| 锁等待次数 | 大于 0 且平均等待超 1 秒 | 业务侧要查锁来源 |
| 临时文件总量 | 单日累计超 1GB | 优先调大 work_mem 再观察 |
| Checkpoint 间隔 | 频繁且耗时高 | 检查 max_wal_size 和磁盘写入 |
这些阈值不是真理,是一份让你有据可依的起点。每种业务对耗时的容忍度不同,运行一个月后把报告里的数字和线上故障时间对上,再回改阈值,比拍脑袋准得多。特别是临时文件这个指标,它和你业务里报表查询的频度强相关,同一套阈值放到 OLTP 和 OLAP 集群上完全是两个世界。
5. 避坑:pgBadger 出真报告前最容易翻车的五个地方
5.1 log_line_prefix 不匹配,一上来就报错
现象:命令执行后立刻报 “Wrong log_line_prefix” 或 “can't parse”,报告文件没生成。
原因:postgresql.conf 里的 log_line_prefix 不是 pgBadger 认识的格式,常见是少了 %t 或 %l,或者手写的格式和实际配置有出入。
解决:先在 psql 里执行 SHOW log_line_prefix;,拿真实输出检查。最简单的做法是把 3.1 的配置原样粘贴并 reload,再等新日志产生后重跑。如果你的前缀必须带业务标识,把 SHOW 输出的字符串原样用 -p 参数传给 pgBadger,两边对齐就不再报错。这属于格式对不上就老老实实改配置的问题,没有玄学空间。
5.2 报告空白但日志明明有内容
现象:报告生成成功,打开 General 页面也有连接数,但 Top Queries 和慢查询区块是空的。
原因:日志里只有连接、断开的记录,没有语句执行时长。log_min_duration_statement 没开,或者设得太大,示例里设 1000 意味着只有超过 1 秒的语句才带 duration 字段,小于 1 秒的语句完全不进日志。
解决:SHOW log_min_duration_statement; 确认值。想分析所有语句,临时设成 0,日志量和报告体积会明显变大,不要一直开着;想找相对慢的,设 100 到 300 更实用。另外注意 log_statement 和 log_min_duration_statement 不要同时开,两者叠加会产生重复记录,报告里的数字是虚胖的。
5.3 报告时间和本地时间对不上
现象:报告的横轴是 UTC 时间,和业务日志、告警平台差 8 小时,对不上号。
原因:PostgreSQL 的 log_timezone 参数控制日志时间戳时区,默认可能跟服务器本地时区不一致;pgBadger 按日志内的 %t 解析,不换算,所以报告时间和日志本身一致,但和你 date 看到的本地时间不一致。
解决:在 postgresql.conf 里把 log_timezone 设成业务所在时区,比如log_timezone = 'Asia/Shanghai',reload 后重新生成的日志就对齐了。历史日志没法补救,只能按差值换算。写 -b/-e 时也带上时区偏移,比如 “2024-06-01 00:00:00+08”,让 pgBadger 明确边界,避免整点对不上。
5.4 增量数字重复,越堆越大
现象:每天的日报里,某个慢查询的总耗时比昨天翻倍,明明线上没有新慢查询。
原因:时间窗没生效,cron 命令里的引号丢了,date 命令实际执行成了两个参数,-b 被 shell 拆掉,pgBadger 退化成解析全部日志;或者日志文件没轮转,天天解析同一批文件。
解决:先手动跑一遍 4.3 的脚本,看生成报告的 General 页时间范围是不是只有昨天一天;再把日志轮转配好,建议 PostgreSQL 侧按天产生新文件,已归档的压缩移走。清理一次全量报告作为新基线,之后增量才会准。增量功能依赖稳定的文件列表,日志文件改个名、目录换位置都会打破它,变更后先跑一次全量。
5.5 日志太大,把磁盘和内存打爆
现象:解析 30GB 日志时,/tmp 写满或内存飙升,机器卡死。
原因:pgBadger 虽然流式读日志,但最后生成 HTML 需要把所有聚合结果写进一个文件,报告本身可能几百 MB;开启多线程并行解析时,每个线程都有自己的缓冲,内存占用成倍上涨;输出路径默认在 /tmp,小分区很快被撑爆。
解决:先用 -s 限制日志读取量探测性能,比如 pgbadger -s 2G -o /tmp/test.html 先跑一小段;把 -O 指向有足够空间的目录,别用 /tmp;日志按天拆开、分批解析成多个报告,再配合归档。压缩日志能减少 I/O,pgBadger 直接支持 .gz 文件,建议轮转时顺手 gzip,既省磁盘又省命令行通配符的长度。
6. 接进告警与抽查验证:用 pg_stat_statements 给报告背书
pgBadger 报告的价值在于快速定位,但别把唯一一份报告当真相。我现在的习惯是:报告出了 Top 慢查询,先去数据库里用 pg_stat_statements 交叉验证一遍。
SELECT query, calls, total_exec_time, mean_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;注意 PostgreSQL 11 之前的字段叫 total_time,之后改成了 total_exec_time,版本不同查询也要跟着改。对照方式很简单:报告里的 Top 语句应该能在上面结果里找到同款,数值量级大致一致。如果报告里某条语句总耗时很高、pg_stat_statements 里却几乎没有,先别急着下结论,检查是不是日志采样窗口和统计窗口不一致,或者语句在日志里被拆成了多行没拼全。报告负责“怀疑”,pg_stat_statements 负责“确认”,两个工具对着看,就不会被单个视角带偏。
关于归档,我还会把每天的 HTML 报告按周打包,保留 90 天,方便追溯“上周五那次抖动是不是同一批慢查询”。打包用 tar 就行,不展开。真要接告警,我会在 cron 脚本里加一行 grep,从当天日志里捞出耗时最大的那条语句,拼成一行文本告警;不要直接解析 HTML,那东西改版一次坏一次。
最后分享一个血泪教训:我刚开始用 pgBadger 时,改完配置直接排了 cron,结果第二天报告全空,查了一圈发现是 reload 之后忘记等新日志产生,历史日志又是旧前缀,白白浪费一天。现在我的固定动作是:改完配置先手动跑一条 10 分钟的小日志,确认报告有数据、时间正确,再上 cron。先小后大、先时间窗后全量、先单文件后通配符,这三条顺序能挡住绝大部分翻车。希望帮到你。
本文还有配套的精品资源,点击获取