Skip to content

CSV 日志分析 ​

csvlog 是 PostgreSQL 内置的结构化日志格式:把 log_destination 设为 csvlog,收集进程就会把每条日志写成一个固定 26 列的 CSV 行。比起肉眼难读的 stderr 前缀拼串,CSV 把「时间、用户、数据库、错误级别、SQLSTATE、消息、语句原文」拆成了可以直接用 SQL 查询的字段——用数据库的能力分析数据库的日志,这正是本页的主题。

CSV 日志的价值在「分析」二字:错误趋势按天聚合、哪个用户在报哪类错、死锁事件的完整现场、连接建立/断开的审计流水,这些需求都能落到一条 SQL 上。配套的读写工具有两件:file_fdw 外部表(把日志文件挂成一张表直接查)与 COPY(把日志灌进分析表慢慢算)。

本页先讲清 26 列的完整结构(这是所有分析的基础),再给出外部表搭建与一组拿来即用的分析 SQL。更现代的 jsonlog(PG15+)字段与本格式完全一致,只是载体换成 JSON,细节在下一页对比展开。

前置知识

建议先阅读:日志轮转与清理

基础概念 ​

启用 CSV 日志只需两行配置(收集进程必须已开启):

conf
logging_collector = on          # 修改需重启
log_destination = 'csvlog'      # 可与其他去向并列,如 'stderr,csvlog'
log_filename = 'postgresql-%Y-%m-%d.log'

文件形态要点:

  • 文件名自动加 .csv 后缀:当 log_destination 含 csvlog 而 log_filename 不以 .csv 结尾时,收集进程会自动追加 .csv。上例实际产出 postgresql-2026-09-30.csv。
  • 每条消息恰好一行:消息内含换行(如带 CONTEXT 的错误)时按 CSV 规则用引号包裹、双引号转义,物理上仍是一行。这保证了 COPY 与外部表读取的行对齐。
  • 多去向并存:'stderr,csvlog' 会同时产出 .log 与 .csv 两套文件,过渡期常用。

为什么是 CSV 而不是「更好看的格式」 ​

格式人读机器处理字段固定依赖
stderr 文本中(依赖 log_line_prefix 配置)差(正则解析)无无
csvlog差(列多行宽)优(SQL/任意 CSV 工具)26 列固定收集进程
jsonlog中优(JSON 生态原生)同 26 字段收集进程,PG15+

csvlog 是「零外部依赖的机器可读格式」:不需要 PG15、不需要日志平台,一台数据库加一个 file_fdw 就能开查。

语法与用法 ​

方式一:file_fdw 外部表(推荐) ​

把日志目录下的 CSV 文件挂成外部表,查询即时生效:

sql
-- 1) 创建扩展与服务器(首次)
CREATE EXTENSION IF NOT EXISTS file_fdw;
CREATE SERVER pglog_server FOREIGN DATA WRAPPER file_fdw;

-- 2) 建外部表,location 指向日志文件
CREATE FOREIGN TABLE pglog (
    log_time             timestamp(3) with time zone,
    user_name            text,
    database_name        text,
    process_id           integer,
    connection_from      text,
    session_id           text,
    session_line_num     bigint,
    command_tag          text,
    session_start_time   timestamp with time zone,
    virtual_transaction_id text,
    transaction_id       bigint,
    error_severity       text,
    sql_state_code       text,
    message              text,
    detail               text,
    hint                 text,
    internal_query       text,
    internal_query_pos   integer,
    context              text,
    query                text,
    query_pos            integer,
    location             text,
    application_name     text,
    backend_type         text,
    leader_pid           integer,
    query_id             bigint
) SERVER pglog_server
OPTIONS ( filename '/var/lib/postgresql/18/main/log/postgresql-2026-09-30.csv', format 'csv' );

-- 3) 直接查询
SELECT error_severity, count(*)
FROM pglog
GROUP BY 1;

外部表的权限体系照常生效:给监控账号 GRANT SELECT ON pglog 即可,无需操作系统权限。

方式二:COPY 灌入分析表 ​

sql
-- 建与 csvlog 同构的分析表(加业务时间戳)
CREATE TABLE csvlog_stage (LIKE pglog INCLUDING ALL) WITH (fillfactor = 90);

-- 批量导入
COPY csvlog_stage
FROM '/var/lib/postgresql/18/main/log/postgresql-2026-09-30.csv'
WITH (FORMAT csv);

外部表适合「即席查询」;COPY 入表适合长期分析(加索引、分区、与监控系统联动)。

详细说明 ​

26 列字段全览 ​

以下是官方定义的 csvlog 完整列结构(按顺序):

序号列名类型含义
1log_timetimestamp(3) with time zone消息记录时间(毫秒精度)
2user_nametext发出消息的用户名
3database_nametext数据库名
4process_idinteger进程号(对应 pg_stat_activity.pid)
5connection_fromtext客户端 IP 与端口(本地连接为空)
6session_idtext会话 ID(等同 %c 的输出)
7session_line_numbigint会话内的日志行号
8command_tagtext命令标签(如 SELECT、UPDATE)
9session_start_timetimestamp with time zone会话(进程)启动时间
10virtual_transaction_idtext虚拟事务 ID,形如 3/4821
11transaction_idbigint顶层事务 ID(无事务为空)
12error_severitytext消息级别:LOG / ERROR / WARNING / FATAL / PANIC 等
13sql_state_codetextSQLSTATE 错误码,如 40P01(死锁)、22012(除零)
14messagetext主消息文本
15detailtext详细信息(如死锁的等待环描述)
16hinttext提示信息
17internal_querytext内部语句(如触发的约束校验 SQL)
18internal_query_posinteger内部语句中出错的位置
19contexttext调用上下文(函数/触发器调用链)
20querytext出错的语句原文(受 log_min_error_statement 控制)
21query_posinteger语句中出错的位置
22locationtext内核源码位置(log_error_verbosity = verbose 时)
23application_nametextapplication_name
24backend_typetext后端类型(client backend、autovacuum worker 等)
25leader_pidinteger并行组 leader 的 PID(PG13+,无则为空)
26query_idbigint查询 ID(PG14+,需内核计算 query id)

版本差异:PG12 及更早为 24 列;PG13 增加 leader_pid;PG14 增加 query_id。用 file_fdw 建外部表时,列数必须与日志版本匹配,否则解析错位。

关键列的解读要点 ​

  • session_id + session_line_num:日志世界的「会话主键」。同一连接产生的所有行(连接、错误、断开)共享 session_id,行号保证顺序,是会话级追踪的完整方案。
  • error_severity:LOG 级行承载检查点、慢语句、autovacuum 等运行信息;ERROR 以上是故障分析的主料。
  • sql_state_code:机器分诊的最佳依据。常见码值如 23505(唯一约束冲突)、40P01(死锁)、57P01(admin shutdown)、42501(权限不足)——按码聚合比按消息文本聚合稳定得多。
  • virtual_transaction_id / transaction_id:与 pg_locks、死锁日志中的事务标识直接对应,方便把日志证据与锁视图互相印证。
  • backend_type:过滤业务日志与后台进程日志的开关(autovacuum 的日志量不小)。
  • query 列的可见性:受 log_min_error_statement 控制,低于阈值不落语句原文;敏感场景的合规评估见上一页。

用 SQL 分析日志的三条主线 ​

sql
-- 主线一:错误总览(级别 × 时间)
SELECT date_trunc('hour', log_time) AS hr,
       error_severity,
       count(*)
FROM pglog
WHERE log_time >= current_date
GROUP BY 1, 2
ORDER BY 1, 3 DESC;

-- 主线二:SQLSTATE 分诊(哪类错误最多)
SELECT sql_state_code,
       count(*) AS cnt,
       max(message) AS sample_message
FROM pglog
WHERE error_severity IN ('ERROR', 'FATAL', 'PANIC')
GROUP BY 1
ORDER BY cnt DESC;

-- 主线三:会话追踪(一条连接的完整轨迹)
SELECT session_line_num,
       log_time,
       error_severity,
       sql_state_code,
       message,
       left(query, 60) AS query_head
FROM pglog
WHERE session_id = '6858ca.1234'
ORDER BY session_line_num;

死锁与 FATAL 事件的取证 ​

CSV 日志把死锁的 DETAIL(等待环描述)完整保留成独立列,检索极其方便:

sql
-- 死锁事件清单(SQLSTATE 40P01)
SELECT log_time,
       database_name,
       user_name,
       process_id,
       message,
       detail
FROM pglog
WHERE sql_state_code = '40P01'
ORDER BY log_time;

-- FATAL 事件(认证失败、连接被杀等)
SELECT log_time,
       user_name,
       connection_from,
       sql_state_code,
       message
FROM pglog
WHERE error_severity = 'FATAL'
ORDER BY log_time DESC
LIMIT 50;

28P01(密码错误)与 28000(认证失败)在 FATAL 行里高频出现时,就是暴力破解尝试的信号,应联动安全告警。

与锁监控、会话视图的交叉印证 ​

CSV 日志的最大优势是「事后」价值:pg_stat_activity 与 pg_locks 是实时快照,事后无从查起,而日志留了全部现场。交叉印证的典型链路:

  1. 日志中锁定一条错误行,取其 process_id 与 session_id。
  2. 若该进程仍存活,直接 SELECT * FROM pg_stat_activity WHERE pid = <process_id> 看现状。
  3. 死锁的 detail 列列出各进程在等哪个事务、被谁阻塞——与 pg_locks 的 transactionid 视图同一套语言(见《pg_locks 锁监控》与《死锁检测机制》)。

pgBadger 等离线分析工具 ​

社区工具 pgBadger 对 csvlog 支持良好,一条命令产出 HTML 报告(Top 查询、错误分布、时间线、连接画像):

bash
pgbadger /var/lib/postgresql/18/main/log/postgresql-2026-09-30.csv -o report.html

它解决了「日志里慢语句的聚合分析」:配合 log_min_duration_statement 开启的慢语句日志,pgBadger 能给出比手写 SQL 丰富得多的报表。轻量即席分析用本页的 SQL,定期全量报告交给 pgBadger,两者不冲突。

版本差异与迁移注意 ​

  • 列结构跨版本变化:升级大版本后,外部表与解析脚本要同步调整列清单(PG13 加 leader_pid、PG14 加 query_id)。
  • transaction_id 的类型:列类型在文档中固定为 bigint,但老版本日志中的事务 ID 语义(32 位 xid)与新版本存在差异,做跨版本日志合并分析时注意口径。
  • 多去向并存时的体积翻倍:'stderr,csvlog' 会让日志体量接近翻倍,轮转与清理容量按双份估算(见《日志轮转与清理》)。

26 列在不同消息类型里的取值形态 ​

同一套列,不同类型的消息填充的列不同。掌握「哪类消息看哪几列」能大幅加快分析:

消息类型关键列典型形态
连接建立(connection authorized)session_id、user_name、connection_from、application_nameerror_severity = LOG,message 含用户与库名
连接断开(disconnection)session_id、session_start_timemessage 含会话时长
检查点 / 运行信息backend_type(checkpointer 等)LOG 级,command_tag 为空
慢语句(duration: X ms)message、queryLOG 级,语句文本拼在 message 里
SQL 错误sql_state_code、message、queryerror_severity = ERROR
死锁sql_state_code = 40P01、detaildetail 列含完整等待环
认证失败error_severity = FATAL、connection_fromSQLSTATE 28P01 / 28000
autovacuum 行为backend_type = autovacuum workermessage 形如 automatic vacuum of table ...

其中「慢语句行」值得单独说明:它由 log_min_duration_statement 触发,耗时与语句文本合并在 message 列里,格式固定为 duration: 123.456 ms statement: SELECT ...,多行语句同样按 CSV 规则转义为物理单行。

慢语句日志行的提取 ​

慢语句的「耗时」要从 message 里用正则提取,这是 csvlog 分析里最常用的技巧:

sql
-- 提取耗时并排序:当日最慢语句 Top 10
SELECT (regexp_match(message, 'duration: ([0-9.]+) ms'))[1]::numeric AS duration_ms,
       log_time,
       user_name,
       left(regexp_replace(message, '^duration: [0-9.]+ ms  statement: ', ''), 80) AS statement_head
FROM pglog
WHERE error_severity = 'LOG'
  AND message LIKE 'duration: %statement: %'
ORDER BY duration_ms DESC
LIMIT 10;
sql
-- 慢语句的小时分布与平均耗时
SELECT date_trunc('hour', log_time) AS hr,
       count(*) AS slow_count,
       round(avg((regexp_match(message, 'duration: ([0-9.]+) ms'))[1]::numeric), 1) AS avg_ms
FROM pglog
WHERE message LIKE 'duration: %statement: %'
GROUP BY 1
ORDER BY 1;

边界提醒:这里提取的是「逐条流水」,与 pg_stat_statements 的聚合视角互补——前者能还原单次慢执行的时间点与参数值,后者回答长期排名。两者的字段对应关系见下一节。

与 pg_stat_statements、pg_stat_activity 的字段对照 ​

csvlog 的列与监控视图的字段有一套稳定的对应关系,交叉取证全靠它:

csvlog 列对应视图字段说明
process_idpg_stat_activity.pid同一进程号体系
session_id(仅日志侧)log_line_prefix 的 %c实时视图不暴露会话 ID,只能靠日志串轨迹
virtual_transaction_idpg_locks.virtualtransaction形如 4/1921
transaction_idpg_stat_activity.backend_xid顶层事务号
query_idpg_stat_activity.query_id ↔ pg_stat_statements.queryid(PG14+)三方联动的关键键
application_name / user_name / database_name同名字段画像维度
error_severity / sql_state_code(无对应)日志独有,错误分诊主键

拿到一条慢语句日志后,若其 query_id 非空,直接去 pg_stat_statements 查该模板的历史画像(平均耗时、调用次数、块读),一次就能判断「这次慢是偶发还是常态」。

历史文件管理与分区分析表 ​

「COPY 入表」做大之后,管理动作要跟上分区与索引:

sql
-- 月度分区分析表(按天导入,只保留高频分析列)
CREATE TABLE csvlog_history (
    log_time timestamp(3) with time zone NOT NULL,
    user_name text, database_name text, process_id integer,
    error_severity text, sql_state_code text, message text,
    query text, application_name text, backend_type text,
    session_id text
) PARTITION BY RANGE (log_time);

CREATE TABLE csvlog_history_2026_09
    PARTITION OF csvlog_history
    FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');

-- 常用查询路径的索引
CREATE INDEX ON csvlog_history (log_time);
CREATE INDEX ON csvlog_history (sql_state_code);
bash
# 每日导入昨日文件(FROM PROGRAM 免去文件名调度难题;后端身份运行可读 0600 文件)
psql -c "COPY csvlog_history (log_time, user_name, database_name, process_id,
        error_severity, sql_state_code, message, query, application_name, backend_type, session_id)
        FROM PROGRAM 'cat /var/lib/postgresql/18/main/log/postgresql-$(date -d yesterday +\\%F).csv'
        WITH (FORMAT csv);"

说明:只导入高频分析的列可以显著缩小表体积,完整 26 列的原始证据交给冷归档;分区按月(量大按天),过期分区 DROP PARTITION 秒级清理。

解析性能与工具选择 ​

csvlog 解析的技术选型按数据量分级:

场景工具理由
单文件即席查询file_fdw 外部表零搬运,SQL 即查
长期分析COPY 入分区表可索引、可聚合、可关联业务数据
脚本轻量统计Python csv 模块正确处理引号内换行与逗号
定期全量报告pgBadger开箱即用的报表
文本流式监控tail -f + 简单 grep仅限「按行匹配」的场景

性能要点:数 GB 的单日 CSV 用外部表全扫一遍要数十秒,高频看板必须走「入表 + 索引」;正则提取慢语句耗时是 CPU 密集操作,先按 message LIKE 'duration: %' 缩小结果集再提取;pgBadger 支持 --jobs 并行解析大文件。

实战示例 ​

示例一:搭建日志外部表并跑通第一个分析 ​

sql
-- 前置:file_fdw 已安装(跨库可用,见 file_fdw 专题页)
CREATE EXTENSION IF NOT EXISTS file_fdw;

-- 服务器对象只需建一次
CREATE SERVER IF NOT EXISTS pglog_server FOREIGN DATA WRAPPER file_fdw;

-- 外部表(26 列,与上表一致)
CREATE FOREIGN TABLE pglog (
    log_time timestamp(3) with time zone, user_name text, database_name text,
    process_id integer, connection_from text, session_id text,
    session_line_num bigint, command_tag text, session_start_time timestamptz,
    virtual_transaction_id text, transaction_id bigint, error_severity text,
    sql_state_code text, message text, detail text, hint text,
    internal_query text, internal_query_pos integer, context text,
    query text, query_pos integer, location text, application_name text,
    backend_type text, leader_pid integer, query_id bigint
) SERVER pglog_server
OPTIONS ( filename '/var/lib/postgresql/18/main/log/postgresql-2026-09-30.csv',
          format 'csv' );

SELECT count(*), min(log_time), max(log_time) FROM pglog;
text
 count  |            min             |            max
--------+----------------------------+----------------------------
 104283 | 2026-09-30 00:00:01.012+08 | 2026-09-30 10:42:33.981+08
(1 row)

示例二:每日错误看板 ​

sql
-- 错误分布:按小时 × SQLSTATE
SELECT date_trunc('hour', log_time) AS hr,
       sql_state_code,
       count(*) AS cnt,
       max(left(message, 50)) AS sample_msg
FROM pglog
WHERE error_severity = 'ERROR'
GROUP BY 1, 2
HAVING count(*) > 5
ORDER BY hr, cnt DESC;
text
         hr          | sql_state_code | cnt |           sample_msg
---------------------+----------------+-----+----------------------------------
 2026-09-30 02:00:00 | 23505          | 412 | duplicate key value violates ...
 2026-09-30 04:00:00 | 40P01          |   3 | deadlock detected
 2026-09-30 09:00:00 | 42501          |  18 | permission denied for table ...
(3 rows)

读法:凌晨的 23505 高峰指向批量任务的重复键问题(应用层缺陷);40P01 出现即触发死锁排查;42501 集中在上午提示权限变更或代码发布引入的越权访问。

示例三:连接审计流水 ​

配合 log_connections = on 与 log_disconnections = on,CSV 日志即审计流水:

sql
-- 连接建立/断开流水(LOG 级消息,按 message 前缀过滤最稳)
SELECT log_time,
       session_id,
       user_name,
       connection_from,
       message
FROM pglog
WHERE message LIKE 'connection authorized%'
   OR message LIKE 'disconnection%'
ORDER BY log_time DESC
LIMIT 20;
sql
-- 按来源统计当日连接数(连接风暴取证)
SELECT connection_from,
       user_name,
       count(*) FILTER (WHERE message LIKE 'connection authorized%') AS connects,
       count(*) FILTER (WHERE message LIKE 'disconnection%')         AS disconnects
FROM pglog
WHERE log_time >= current_date
  AND (message LIKE 'connection authorized%' OR message LIKE 'disconnection%')
GROUP BY 1, 2
ORDER BY connects DESC;

connects 与 disconnects 的差值即「当日净增连接」,长期为正的来源就是连接泄漏嫌疑对象;审计留存策略见《log_connections / log_disconnections》一页。

示例四:慢语句 Top 榜(当日) ​

sql
-- 当日最慢的 10 条语句(含耗时与语句头)
SELECT round(d.duration_ms, 1) AS ms,
       d.log_time,
       d.user_name,
       left(d.statement_head, 70) AS statement_head
FROM (
    SELECT (regexp_match(message, 'duration: ([0-9.]+) ms'))[1]::numeric AS duration_ms,
           log_time, user_name,
           regexp_replace(message, '^duration: [0-9.]+ ms  statement: ', '') AS statement_head
    FROM pglog
    WHERE log_time >= current_date
      AND message LIKE 'duration: %statement: %'
) d
ORDER BY d.duration_ms DESC
LIMIT 10;
text
     ms    |          log_time           | user_name |                  statement_head
-----------+-----------------------------+-----------+---------------------------------------------------
 18422.334 | 2026-09-30 02:14:07.882+08  | etl_user  | SELECT ... FROM events JOIN ...
  4210.101 | 2026-09-30 09:41:22.101+08  | app       | SELECT * FROM orders WHERE ...
(2 rows)
sql
-- 对照历史画像:这条语句平时也这么慢吗(PG14+,query_id 关联)
SELECT calls, round(mean_exec_time::numeric, 1) AS mean_ms, round(max_exec_time::numeric, 1) AS max_ms
FROM pg_stat_statements
WHERE query LIKE 'SELECT ... FROM events JOIN%';

两条解读路径:凌晨的 ETL 慢语句若与历史均值接近,属于「已知的重查询」,走优化排期;上午那条远超历史 max_exec_time 的,才是当日需要现场介入的异常。

示例五:bash 巡检(Python 解析版) ​

引号内换行让裸文本工具不可靠,轻量巡检用 Python 的 csv 模块最稳:

bash
#!/usr/bin/env bash
# csvlog_hourly.sh —— 最近一小时的错误分诊摘要
LOG=/var/lib/postgresql/18/main/log/postgresql-$(date +%F).csv
python3 - "$LOG" <<'EOF'
import csv, sys, collections
import datetime as dt

one_hour_ago = dt.datetime.now(dt.timezone.utc) - dt.timedelta(hours=1)
cnt = collections.Counter()
sample = {}
with open(sys.argv[1], newline='') as f:
    for row in csv.reader(f):
        if len(row) < 14:
            continue
        try:
            t = dt.datetime.fromisoformat(row[0])
        except ValueError:
            continue
        if t < one_hour_ago:
            continue
        sev, code, msg = row[11], row[12], row[13]
        if sev in ('ERROR', 'FATAL', 'PANIC'):
            cnt[(sev, code)] += 1
            sample.setdefault((sev, code), msg[:80])
for (sev, code), n in cnt.most_common(10):
    print(f"{sev:6} {code or '-':6} {n:6}  {sample[(sev, code)]}")
EOF
text
ERROR  23505     412  duplicate key value violates unique constraint ...
FATAL  28P01      18  password authentication failed for user ...
ERROR  40P01       3  deadlock detected

脚本要点:csv.reader 天然处理引号转义;列下标按 26 列顺序硬编码(error_severity 是第 12 列、sql_state_code 是第 13 列),跨版本使用时同步核对列清单。

示例六:pgBadger 批量与增量报告 ​

csvlog 是 pgBadger 支持最好的格式,--jobs 并行 + 增量模式适合大日志量:

bash
# 一次性全量报告:8 并行解析当日 CSV
pgbadger -j 8 --prefix '' -o report-$(date +%F).html \
    /var/lib/postgresql/18/main/log/postgresql-2026-09-30.csv

# 增量模式:按天滚动生成,聚合出周/月视图(-I 开启增量,-O 指定输出目录)
0 6 * * * pgbadger -I -O /var/www/pgbadger \
    /var/lib/postgresql/18/main/log/postgresql-$(date -d yesterday +\%F).csv

说明:pgBadger 能自动识别标准 csvlog 的列结构,--prefix '' 仅用于明确「无自定义前缀」(csvlog 默认即可识别,显式写出是防御配置漂移);增量模式下 -O 目录里会出现按周期的汇总文件,pgbadger --rebuild 可在数据缺失时重建聚合。报告覆盖 Top 查询、锁等待、检查点与会话分布,与本文的 SQL 分析互补——SQL 侧做精确取证,pgBadger 侧做全景速览。

示例七:错误趋势周报(一条 SQL) ​

把当周每天的 SQLSTATE 分布拉成趋势,接邮件或看板都行:

sql
SELECT log_time::date AS day,
       sql_state_code,
       count(*) AS cnt,
       min(left(message, 50)) AS sample_msg
FROM pglog
WHERE log_time >= current_date - interval '7 days'
  AND error_severity IN ('ERROR', 'FATAL')
GROUP BY 1, 2
ORDER BY 1 DESC, cnt DESC;
text
    day     | sql_state_code | cnt  |                 sample_msg
------------+----------------+------+---------------------------------------------
 2026-09-30 | 23505          |  412 | duplicate key value violates unique ...
 2026-09-30 | 28P01          |   18 | password authentication failed for ...
 2026-09-29 | 23505          |  398 | duplicate key value violates unique ...
(3 rows)

进阶:外层套窗口函数算「环比」,cnt - lag(cnt) OVER (PARTITION BY sql_state_code ORDER BY day),把环比突增(比如 +200%)的 SQLSTATE 单独标红——同一错误类别的异常增长比绝对计数更能暴露新故障。

注意事项 ​

列数必须与版本匹配。 外部表少了列(老版本日志新列)会导致整行解析失败;升级大版本后第一时间核对 csvlog 列清单。

单行可能非常宽。 带 CONTEXT、QUERY 的大错误一行可达数 MB,外部表扫描与 COPY 都要吃内存与 I/O;log_error_verbosity 的取舍要考虑这一层。

query 列不是默认总有值。 只有达到 log_min_error_statement 阈值的错误才带语句原文;以为「日志里必有 SQL」的分析脚本要处理空值。

CSV 文件不可在写入中重命名。 外部表按文件路径读取,正在写入的当天文件可以查,但被 logrotate 改名的文件会瞬间「消失」——分析历史文件时用改名后的稳定路径,或先 COPY 入表。

权限是两层。 外部表只解决数据库内的权限;文件系统层面日志文件属 postgres 用户、0600 权限,file_fdw 读取以后端进程身份进行,与运行用户一致,无需额外系统权限,但不要把日志目录权限放宽。

大文件查询有成本。 数 GB 的单日 CSV 全扫一遍不便宜;高频分析应该走「COPY 入表 + 索引/分区」,外部表留给即席查询。

多行消息在物理上仍是一行。 不要用 grep 按行切分处理 csvlog——引号内的换行会骗过 naive 文本工具。解析交给 SQL、Python csv 模块或成熟工具。

最佳实践 ​

  1. log_destination = 'csvlog' 作为生产默认。 它以零外部依赖提供了机器可读日志,分析门槛从「正则表达式」降到「SELECT」。
  2. 用 file_fdw 外部表做即席分析,用 COPY 入表做长期分析。 前者零搬运,后者可建索引、可关联业务数据;两条路径共享同一份 26 列结构定义。
  3. 分析按 sql_state_code 聚合而不是按消息文本。 错误码稳定、文本会随版本与参数变化;基于文本的告警在升级后最容易失效。
  4. 死锁、FATAL、checksum 类事件设为即时告警。 这些是低频高危事件,等日报就来不及了;40P01、28P01 暴增、57P03 都有明确含义。
  5. 把会话 ID 纳入排障话术。 应用报障时先拿时间与用户过滤出 session_id,再拉整条会话轨迹,比「看日志找感觉」快一个量级。
  6. 与 pg_stat_statements、pg_stat_activity 形成三视角。 日志看「发生了什么」,实时视图看「正在发生什么」,扩展统计看「长期谁最重」——三者的语言(pid、queryid、session_id)在 csvlog 里都有对应列。
  7. 定期报告交给 pgBadger,临时取证用 SQL。 不要为已有工具能做的事情手写脚本,也不要指望 pgBadger 替你做业务定制分析。
  8. 外部表定义进版本库,升级时同步改列。 csvlog 列清单是版本敏感物,与数据库版本一起管理。
  9. 日志分析账号用只读权限 + 独立外部表。 控制能查哪些日志文件(外部表按文件粒度授权),避免所有人在所有文件上无差别查询。
  10. 大版本升级后重验解析链路。 列增删、文件命名、轮转行为都可能有变,升级清单里加一项「跑通示例一的外部表查询」。

下一节

继续学习:JSON 日志(jsonlog)

参考链接 ​