log_connections / log_disconnections
如果说语句日志回答的是「数据库里执行了什么」,那么连接日志回答的就是「谁进来了、什么时候离开的」。log_connections 与 log_disconnections 这对开关负责记录每一次客户端连接的建立与断开,是审计链路里最靠近「边界」的一环:IP 伪造不了 TCP 源地址、绕不开认证握手,因此连接日志是追溯账号滥用、定位异常连接源最可靠的证据。
这对参数默认都是关闭的。许多团队把语句日志开得很重,却对「谁连了进来」一无所知——而现实中的安全事件里,「用失窃的账号在凌晨三点从陌生网段连进来」远比「在授权会话里跑了一条诡异 SQL」常见。连接日志便宜(每条连接只产生两三行日志,与流量无关)、难以规避(不依赖语句内容),是性价比最高的一类审计日志。
本页覆盖两个参数的行为与日志形态、认证失败的记录机制、pg_stat_database 的会话统计、连接池场景下的日志稀释问题,以及把连接日志接入 ELK / Loki 集中分析的思路,最后用一个「排查异常连接来源」的实战把它们串起来。
前置知识
建议先阅读:log_statement 语句日志
基础概念
连接审计涉及的核心参数与视图如下:
| 参数 / 视图 | 默认值 | 作用 |
|---|---|---|
log_connections | off | 记录每次连接尝试与认证授权完成 |
log_disconnections | off | 记录会话结束及会话时长 |
log_line_prefix | — | 日志行首元信息,连接日志归因的前提 |
log_checkpoints | PG15 起默认 on | 记录 checkpoint 活动(运维事件,非连接事件) |
pg_stat_database.sessions | — | 每个数据库的会话总数等统计(PG14+) |
pg_stat_database.stats_reset | — | 统计上次重置时间 |
连接日志的两段式记录
开启 log_connections 后,一次成功的连接会在日志中留下两条记录:
connection received:—— 服务器收到连接请求,记录来源host与端口。这一步发生在认证之前,无论后续认证是否成功(配合开关),它都能告诉你「谁在敲门」。connection authorized:—— 认证与授权完成,记录用户名、数据库名,以及客户端自报的application_name。
两段式的设计意味着:一段日志负责「来源」,一段日志负责「身份」。把两段用进程号(%p)串起来,就得到一条完整的连接证据链。
断开日志与会话时长
log_disconnections 在会话结束时记录一条 disconnection:,其中包含会话总时长:
LOG: disconnection: session time: 0:41:02.871 user=app database=appdb host=10.0.3.17对审计而言,会话时长是一个很有用的行为特征:正常的应用连接是「短连接高频、分钟级」,而「挂了 8 小时的交互式 psql 会话」「只在凌晨出现的短会话」都是值得关注的异常模式。
认证失败:不需要开关的日志
一个常见误解是「不开 log_connections 就看不到登录失败」。事实相反:认证失败的错误消息始终会以 LOG / FATAL 级别写入服务器日志,与这两个开关无关:
2026-10-07 03:12:44.101 CST [31112] LOG: connection received: host=203.0.113.9 port=60122
2026-10-07 03:12:44.355 CST [31112] FATAL: password authentication failed for user "postgres"也就是说,暴力破解尝试天然会被记录,不需要任何额外配置。log_connections 增补的是「成功连接」的可见性——两者合起来才是完整的连接审计面。
连接生命周期与日志时点
把一次连接从建立到销毁的全部日志时点画在一条时间线上,排障与审计时按图索骥:
客户端 ──────TCP 建连──────► 服务器
│
├─ (1) connection received: host=… port=… ← log_connections
│
├─ (2) 认证:匹配 pg_hba.conf → scram-sha-256 / cert …
│ ├─ 失败:FATAL password authentication failed ← 恒定落盘
│ └─ 成功 ↓
├─ (3) connection authorized: user=… db=… app=… ← log_connections
│
├─ (4) 会话期间:语句日志 / pgaudit / 错误消息
│
└─ (5) disconnection: session time: … ← log_disconnections| 时点 | 日志行 | 触发条件 | 回答的问题 |
|---|---|---|---|
| (1) | connection received: | log_connections = on | 谁在敲门 |
| (2) 失败 | FATAL: password authentication failed | 恒定 | 谁在试图冒充谁 |
| (3) | connection authorized: | log_connections = on | 谁连上了哪个库 |
| (5) | disconnection: | log_disconnections = on | 会话活了多久 |
(1) 与 (3) 之间可能存在秒级间隔——认证握手、TLS 协商、外部认证服务(LDAP / Kerberos)都可能耗时。排查「登录慢」时对比两个时间戳,即可判断耗时发生在认证阶段还是连接排队阶段。
语法与用法
基础配置
# postgresql.conf
# 记录连接建立(received + authorized 两段)
log_connections = on
# 记录会话结束与会话时长
log_disconnections = on
# 行首前缀:时间 [进程号] 用户@库 —— 连接日志归因的最低要求
log_line_prefix = '%m [%p] %q%u@%d '
# (可选)日志输出形式;PG15+ 可选 jsonlog 便于机读采集
logging_collector = on
log_destination = 'stderr'这两个参数不能在会话内用 SET 临时修改,需要写入配置并重载:
pg_ctl reload -D /var/lib/postgresql/18/main
# 或
psql -U postgres -c "SELECT pg_reload_conf();"-- 验证
SHOW log_connections;
SHOW log_disconnections;成功连接的完整日志形态
2026-10-07 11:02:18.553 CST [31200] LOG: connection received: host=10.0.3.17 port=52913
2026-10-07 11:02:18.590 CST [31200] LOG: connection authorized: user=app database=appdb application_name=order-service
2026-10-07 11:02:59.441 CST [31200] app@appdb LOG: disconnection: session time: 0:00:40.887 user=app database=appdb host=10.0.3.17注意第二行与第三行因为 log_line_prefix 里的 %u@%d 已经带上了用户与库名;第一行(received)发生在认证之前,用户与库还是未知的,这正是 %q 哨兵让 %u@%d 自动省略的效果——不会输出空字段。
会话内自证来源的 SQL
审计不止可以看日志,连接里的会话自己也能报告来源信息,用于现场核对与脚本取证:
SELECT inet_client_addr() AS client_ip,
inet_client_port() AS client_port,
session_user AS login_role,
current_user AS current_role,
current_database() AS dbname,
current_setting('application_name') AS app_name,
pg_backend_pid() AS pid; client_ip | client_port | login_role | current_role | dbname | app_name | pid
-----------+-------------+------------+--------------+--------+------------+-------
10.0.3.17 | 53120 | app | app | appdb | order-svc | 31901session_user 是认证登录用的角色(谁连进来的),current_user 是当前生效角色(SET ROLE 之后会变化)——审计口径应优先用 session_user,防止换壳后归因错位。本地 Unix socket 连接时 inet_client_addr() 返回 NULL,这是识别「本机连接」的简单方法。这组函数在业务留痕场景也常用:把来源 IP 直接写进操作的审计表,比事后翻日志便宜。
会话统计视图
-- 每个数据库的会话计数与时长统计(PG14+ 列)
SELECT datname,
numbackends,
sessions,
sessions_abandoned,
sessions_fatal,
sessions_killed,
round(sessions_time::numeric / 1000, 1) AS total_session_seconds,
stats_reset
FROM pg_stat_database
WHERE datname NOT LIKE 'template%'; datname | numbackends | sessions | sessions_abandoned | sessions_fatal | sessions_killed | total_session_seconds | stats_reset
-----------+-------------+----------+--------------------+----------------+-----------------+-----------------------+------------------------------
appdb | 6 | 419882 | 3 | 11 | 7 | 1.0368742e+09 | 2026-09-01 00:00:00.00008+08
postgres | 1 | 2931 | 0 | 2 | 1 | 41210.881 | 2026-09-01 00:00:00.00008+08sessions_abandoned(客户端异常断开)、sessions_fatal(服务端 FATAL 终止)、sessions_killed(被 pg_terminate_backend 等主动终止)三个计数器把「异常断开的会话」从总量里拆了出来,是连接健康度的直接指标。
详细说明
两段日志分别回答什么问题
connection received 与 connection authorized 的分工值得再强调一遍,因为它们在审计中的用法不同:
| 日志段 | 时机 | 关键字段 | 回答的问题 |
|---|---|---|---|
connection received: | 认证前 | host、port | 谁在敲门?来自哪个网段?敲门频率是否异常? |
connection authorized: | 认证后 | user、database、application_name | 哪个账号连上了哪个库?自称是什么应用? |
只关心「扫描与爆破」时,received 段就够了;追溯「已授权账号的行为」时,需要把 authorized 段与该进程后续的语句日志按 PID 关联。这也是 log_line_prefix 必须包含 %p 的原因——PID 是把同一次连接的多行日志串成证据链的钥匙。
跨行关联:会话 ID 与行号
PID 之外,log_line_prefix 还有两个专门为跨行关联设计的转义符:
%c:会话 ID,形如689e4c1e.5618(前半是会话开始的十六进制时间戳,后半是 PID 的十六进制)。跨日志轮转、跨主机的场景下比裸 PID 更稳——PID 会被操作系统复用。%l:每个会话内的日志行号,用于确认「有没有丢行」,也是判断采集链路是否截断的依据。
启用两者的前缀写法与日志形态:
log_line_prefix = '%m [%p] %c-%l %q%u@%d '2026-10-07 11:02:18.553 CST [31200] 689e4c1e.7a20-1 LOG: connection received: host=10.0.3.17 port=53120
2026-10-07 11:02:18.590 CST [31200] 689e4c1e.7a20-2 LOG: connection authorized: user=app database=appdb application_name=order-service实践建议:stderr 文本日志的实例加 %c 收益明显(跨轮转文件追踪一次事件的所有行);接入 ELK / Loki 的 jsonlog 实例自带 session_id 结构化字段,无需重复。
会话时长的统计口径
disconnection: 里的 session time 从会话进程启动(收到连接)计起,到会话结束为止,包含认证耗时、空闲等待与事务执行的全部时间。因此:
- 「短会话 + 高频连接」模式(每会话几秒)通常意味着应用没有使用持久连接或连接池,审计上要结合业务确认是否为预期行为。
- 「超长会话 + 近零活动」要结合
pg_stat_activity的state(idle/idle in transaction)判断是悬挂连接还是滞留的事务。 idle_in_transaction_session_timeout(以及 PG14+ 的idle_session_timeout)触发的终止会以FATAL级别写入日志(例如空闲事务超时导致连接被强制结束),配合断开日志可以完整还原「会话为什么死掉了」。
认证失败日志的边界
失败认证永远落盘,但有两点需要知道:
FATAL: password authentication failed for user "xxx"是固定形态,配合connection received行可以还原「哪个 IP 在对哪个账号尝试口令」。据此可以统计爆破源的 Top N 并反馈到网络层封禁。- 如果还想看到「这次失败匹配了
pg_hba.conf的哪一行」,需要更高的调试级别(详情信息在调试级别日志中输出),生产环境一般不为这个目的调低日志阈值,用log_connections+ 失败消息已经足够。
pg_hba.conf 与连接审计的分工
连接日志记录「发生了什么」,pg_hba.conf 决定「允许发生什么」,两者配合才是完整的边界管控:
# pg_hba.conf 片段:最小网段 + 强认证 + 显式拒绝
# TYPE DATABASE USER ADDRESS METHOD
hostssl appdb app 10.0.3.0/24 scram-sha-256
hostssl appdb ops_admin 10.0.8.0/28 cert
host all all 0.0.0.0/0 reject工程要点:
- 规则自上而下首次匹配:被前面的规则拦下的连接不会落到后面的规则;「为什么这个 IP 连不上」的排查要按顺序逐行核对。
reject收尾:显式拒绝兜底让边界意图在配置里可读,也避免依赖隐式的默认拒绝行为。hostssl强制 TLS:传输加密与连接审计共同构成边界,与安全章节 SSL 相关页面呼应。- 认证方法演进:口令认证推荐
scram-sha-256(由password_encryption控制存储格式);证书认证(cert)把身份绑定到客户端证书,来源可信度更高。
审计侧的用法:把 pg_hba 的规则清单维护成「合法来源 × 账号 × 方法」基线,连接日志里出现基线外的组合即告警——规则文件定义边界,日志验证边界真的在起作用。
连接审计能回答的四类问题
把日志形态与审计需求对齐,连接日志主要覆盖:
- 异常连接源分析:新 IP、陌生网段、非业务时段的连接。做法是把
connection authorized行按host聚合,与业务网段清单比对。 - 账号滥用发现:交互式运维账号(如
postgres、dba)被应用服务器使用、单一账号从几十个来源同时连接、application_name与账号的业务角色不匹配(例如app_rw账号自报psql)。 - 权限回收的验证:离职人员的账号、下线的旧服务,从连接日志里可以直接看到「最后一次连接是什么时候」,为账号清理提供依据。
- 会话行为画像:结合会话时长与语句日志,区分「人类交互会话」与「程序会话」,为不同会话类型匹配不同的语句审计挡位。
pg_stat_database 的会话统计与重置
sessions 系列列(PG14+)是累计计数器,与日志互补的用法是:
- 日志回答「每一次」的问题,计数器回答「趋势」的问题。例如
sessions_killed突增说明有人(或监控脚本)在大量终止会话,先看计数器发现异常,再去日志里找 PID 与 SQLSTATE。 stats_reset标记统计上次清零的时间,做周期性快照(如每小时一次存入监控库)后求差,即可得到各数据库每小时的会话增速,这比解析日志便宜得多。- 重置用
pg_stat_reset_shared('database')(重置集簇级统计)——注意它会影响所有基于累计值的监控基线,安排在维护窗口执行。
与 pg_stat_activity 的配合
pg_stat_activity 是「现在进行时」,连接日志是「过去时」。排查进行中的可疑连接时,两者相互印证:
SELECT pid,
usename,
datname,
application_name,
client_addr,
backend_start,
state,
state_change,
query
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY backend_start;backend_start 是该后端会话的建立时间,与日志里的 connection received/authorized 时间一一对应;client_addr 与日志的 host 对应。审计回溯时,先在日志中锁定可疑 PID,再用 PID 查 pg_stat_activity(若会话还活着)拿到它正在执行的语句。
IPv6 与 NAT 环境的来源记录
两个网络环境细节会影响来源分析的准确性:
- IPv6 地址:
%h与结构化日志的remote_host可能出现 IPv6 长地址,聚合脚本的正则要同时兼容 IPv4 / IPv6。log_hostname = on时%h会输出反向解析的主机名——它引入 DNS 查询开销与依赖,一般保持默认off用 IP 归因,主机名交给 CMDB / 资产表映射。 - NAT / 出口网段:经过 NAT 的连接在日志里表现为网关 IP,大量客户端共享同一来源。此时来源分析要下沉到应用层(application_name、request id),或让各业务子网使用独立出口——这在规划网络时就值得提出来,而不是审计失效后再改。
连接池场景下的日志稀释
连接池会把「谁在用」稀释掉
启用 PgBouncer 等连接池后,日志里看到的是池子与数据库之间的连接:几十个应用真实会话复用少数后端连接,connection authorized 记录的 user 是池子配置的服务端用户,来源永远是池子所在的主机。每一条后端连接背后可能是任意一个客户端。
应对思路:
- 让客户端身份穿透池子:PgBouncer 会在建立后端连接时把客户端的
application_name透传给服务器(配合服务器端application_name_add_host = on还能附上来源主机),这样日志与pg_stat_activity中能区分「是哪个应用接的入」。事务级/语句级池化下,会话级信息仍然有限,这是池化模式本身的代价。 - 审计重点前移:池子场景下,数据库侧的连接日志负责「池子 ↔ 数据库」这一段,客户端到池子那一段的审计要靠池子自己的日志(PgBouncer 有独立的连接日志)。
- 压低连接日志的噪音预期:池子会显著降低连接建立频率,这既是日志稀释也是日志减负——连接审计在池化架构中的权重下降,语句审计(前两页)与对象审计(pgaudit)的权重相应上升。连接池的选型与行为详见连接池章节的 PgBouncer 一页。
认证超时与慢连接的治理
连接日志里有一类容易被误判的记录:连接建立后迟迟不完成认证。这类会话由 authentication_timeout(默认 1min)兜底——认证流程超时后,后端进程被强制终止并留下 FATAL 级日志(措辞形如 authentication timeout,随版本略有差异):
# postgresql.conf:认证窗口
authentication_timeout = 1min # 认证必须在 1 分钟内完成(默认值)审计视角下,这类记录有明确的解读路径:
- 负载均衡与健康检查探针:只建 TCP 连接不发认证包,会持续产生 timeout 记录——把这些来源 IP 登记为白名单,并让探活配置做更完整的探测,比在告警里挨个排除省心。
- 端口扫描:陌生来源的 timeout 记录就是扫描行为,与
connection received行合并统计即得「敲门但进不来」的清单。 - 网络故障:应用服务器到数据库之间的 MTU、防火墙会话老化问题也会以半开连接的形式出现,特征是集中在特定网段并伴随重传。
用一条查询排查「疑似停滞在认证阶段」的连接——未完成认证的会话在 pg_stat_activity 中没有用户名:
SELECT pid, client_addr, backend_start,
now() - backend_start AS age
FROM pg_stat_activity
WHERE backend_type = 'client backend'
AND usename IS NULL
ORDER BY age DESC;与它互补的是会话建立后的两条超时线:idle_in_transaction_session_timeout(悬挂事务)与 idle_session_timeout(PG14+,纯空闲会话),加上客户端侧的 connect_timeout,共同构成「连接生命周期的时间边界」。边界触发的终止都可在日志中按 PID / 会话 ID 还原全过程——这正是前文强调统一前缀的又一处回报。
连接日志里的 TLS 信息
log_connections 开启时,成功授权的连接行会附带 TLS 信息(协议版本、套件、密钥位数,字段随版本略有差异),让「谁在用什么加密方式连我」变得可盘点:
LOG: connection authorized: user=app_ro database=appdb address=10.0.2.15:52100
application_name=billing SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)三个直接用途:
- TLS 治理清单:正则提取
SSL connection出现的行,与全量授权行做差集,得到「仍在明文连接的账号 × 来源」清单——这就是 SSL 章节「传输加密基线」的落地核对手段。 - 证书轮换验证:轮换证书后观察新连接行的协议版本与套件,确认协商结果符合预期(例如禁用旧协议版本后不再出现 TLSv1.2)。
- 合规抽查:审计方常问「传输是否加密」,这条日志行就是实例级的直接证据。
局限:该信息只出现在成功授权的连接行,认证失败的连接没有;「明文连接清单」要基于授权行全量做差集,不能只扫带 SSL 字样的行。
注意事项
log_checkpoints 与运维日志的边界
log_checkpoints 记录的是 checkpoint 的开始/结束与写入统计,属于「运维事件日志」,与连接审计无关,但它默认值的变化值得一提:PG15 起该参数默认值为 on(此前默认 off),因此新版本实例的日志里会周期性出现 checkpoint 记录。理解这一点有两个意义:一是做日志量预算时要把这部分算进去;二是做日志分析(比如统计连接行数)时,要按消息模式过滤,不能简单按行数估算连接量。
日志聚合:ELK / Loki 概述
连接日志的价值随「留存时长」和「可检索性」增长,单机文本文件很快不够用。通用做法是把 PostgreSQL 日志接入集中式日志平台:
- 前置条件:统一
log_line_prefix,或直接启用log_destination = 'jsonlog'(PG15+)让每行日志天然带结构化字段(user、dbname、remote_host、session_id 等),采集与解析成本大幅降低。PG15 之前常用csvlog。 - 采集链路:Filebeat / Fluent Bit / Vector / Promtail 等采集器负责文件尾随与转发,rsyslog/syslog 也是经典方案(
log_destination = 'syslog')。 - ELK(Elasticsearch + Logstash + Kibana):适合全文检索与复杂聚合,按
host、user、application_name做连接来源的时序分析都很方便。 - Grafana Loki:标签化 + 轻量存储,配合 LogQL 可以快速做出「每分钟连接数按来源 IP 分布」这类看板,资源占用低。
平台选型不是本页重点;关键约束是:审计日志的集中存储要有保留期策略与访问控制,并且 PostgreSQL 主机上的本地文件在采集确认成功之前不要清理。
监控与告警的落地模式
连接日志的告警指标不需要复杂推导,四条规则覆盖绝大多数异常:
rule 1 auth_fail_rate
同一 remote_host 认证失败 > 10 次 / 1 分钟 → P2 + 自动封禁
rule 2 new_source
(remote_host, user, application_name) 组合首次出现 → P3 通知
rule 3 offhours_login
运维账号清单内的 authorized 时间在 09:00-20:00 之外 → P2
rule 4 conn_storm
单来源授权连接速率 > 基线均值 × 3 → P3 排查落地步骤:先跑两周「只记录不告警」的观察期,统计各指标的正常分布;据此定阈值与白名单;上线后每月复核误报率并回写规则。规则 2(新组合告警)是最省心的通用规则——基线即白名单,不需要为每类异常单独建模。
实战示例
示例一:开启连接审计并验证两段日志
# postgresql.conf
log_connections = on
log_disconnections = on
log_line_prefix = '%m [%p] %q%u@%d 'psql -U postgres -c "SELECT pg_reload_conf();"# 从另一台机器发起一次连接
psql "host=pg-prod-1 dbname=appdb user=app" -c "SELECT 1;"2026-10-07 11:30:02.101 CST [31901] LOG: connection received: host=10.0.3.17 port=53120
2026-10-07 11:30:02.130 CST [31901] LOG: connection authorized: user=app database=appdb application_name=psql
2026-10-07 11:30:02.135 CST [31901] app@appdb LOG: disconnection: session time: 0:00:00.034 user=app database=appdb host=10.0.3.17三行日志构成一条完整证据:来源 IP → 账号/库/应用 → 会话时长。
示例二:统计连接来源 Top N 与失败认证 Top N
# 今天的授权连接按来源 IP 聚合
grep 'connection authorized' postgresql-2026-10-07.log \
| sed -E 's/.*host=([0-9a-fA-F:.]+).*/\1/' \
| sort | uniq -c | sort -rn | head 5312 host=10.0.3.17
2240 host=10.0.3.18
61 host=10.0.8.4
2 host=203.0.113.9# 认证失败按 (host, user) 聚合 —— 直接暴露爆破源与被尝试的账号
grep 'password authentication failed' postgresql-2026-10-07.log \
| sed -E 's/.*host=([0-9a-fA-F:.]+).*user "([^"]+)".*/\1 \2/' \
| sort | uniq -c | sort -rn | head 318 host=203.0.113.9 user "postgres"
47 host=203.0.113.9 user "admin"
2 host=10.0.3.17 user "app"结果里 203.0.113.9 对 postgres 账号的数百次失败尝试就是典型的爆破行为,应立即反馈到防火墙与 pg_hba.conf 的访问控制(如收紧允许网段)。
示例三:排查一条可疑连接的完整轨迹
假设监控发现 10.0.8.4(本不在业务网段清单内)建立了连接:
# 1. 从日志锁定 PID 与身份
grep '203.0.113\|10.0.8.4' postgresql-2026-10-07.log | head2026-10-07 03:41:15.922 CST [32177] LOG: connection received: host=10.0.8.4 port=55817
2026-10-07 03:41:15.940 CST [32177] LOG: connection authorized: user=dba_ops database=appdb application_name=psql
2026-10-07 03:41:20.114 CST [32177] dba_ops@appdb LOG: disconnection: session time: 0:00:04.192 user=dba_ops database=appdb host=10.0.8.4# 2. 用 PID 拉出该会话的语句日志(若 log_statement 对该角色开启)
grep '\[32177\]' postgresql-2026-10-07.log2026-10-07 03:41:15.940 CST [32177] LOG: connection authorized: user=dba_ops database=appdb application_name=psql
2026-10-07 03:41:16.010 CST [32177] dba_ops@appdb LOG: statement: SELECT email, id_card FROM customers LIMIT 100;
2026-10-07 03:41:20.114 CST [32177] dba_ops@appdb LOG: disconnection: session time: 0:00:04.192 user=dba_ops database=appdb host=10.0.8.4凌晨、陌生网段、运维账号、批量读取敏感列——四个信号叠加,按事件响应流程立案处理:核对值班记录、冻结该账号、在交换机/防火墙侧溯源该主机。如果会话当时还在,用 SELECT * FROM pg_stat_activity WHERE pid = 32177; 可以直接看到正在执行的语句。
示例四:用会话统计监控异常终止趋势
-- 每小时快照一次,落到监控库做差值
CREATE TABLE db_conn_snap (
taken_at timestamptz NOT NULL DEFAULT now(),
datname text NOT NULL,
sessions bigint,
sessions_abandoned bigint,
sessions_fatal bigint,
sessions_killed bigint
);
INSERT INTO db_conn_snap (datname, sessions, sessions_abandoned, sessions_fatal, sessions_killed)
SELECT datname, sessions, sessions_abandoned, sessions_fatal, sessions_killed
FROM pg_stat_database
WHERE datname = 'appdb';-- 最近一小时的异常终止增量
SELECT sum(sessions_fatal) AS fatal_delta,
sum(sessions_killed) AS killed_delta,
sum(sessions_abandoned) AS abandoned_delta
FROM db_conn_snap
WHERE taken_at > now() - interval '1 hour';计数器突增再去日志里按时间窗口捞 FATAL 行,定位成本远低于在日志里盲目搜索。
示例五:jsonlog 采集链路的端到端验证
log_destination = 'jsonlog'
logging_collector = on
log_connections = on
log_disconnections = onreload 后发起一次测试连接,日志文件应出现 JSON 行:
{"timestamp":"2026-10-07 12:10:02.101 CST","user":"app","dbname":"appdb",
"pid":32201,"remote_host":"10.0.3.17","session_id":"689e58aa.7db9",
"level":"LOG","message":"connection authorized: user=app database=appdb application_name=order-service"}采集管道示意(Promtail / Vector / Filebeat 类工具,配置语法以各工具文档为准):
json 日志文件 → 解析 JSON → 保留 user / dbname / remote_host / session_id 为标签
→ 按 message 前缀分流:connection 行 / 语句行 / AUDIT 行
→ 写入日志平台(Loki / Elasticsearch),按天建索引验证清单:平台里能按 remote_host 聚合出当日连接数;能按 session_id 拉出单会话的全部日志行;人为断开采集器 5 分钟,恢复后确认缺口告警触发或有补采。三步都通过,采集链路才算验收。
示例六:连接来源基线的维护
CREATE TABLE conn_baseline (
remote_host inet NOT NULL,
usename text NOT NULL,
app_name text NOT NULL,
approved_by text NOT NULL,
approved_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (remote_host, usename, app_name)
);
INSERT INTO conn_baseline (remote_host, usename, app_name, approved_by) VALUES
('10.0.3.17', 'app', 'order-service', 'sec-review-2026-09'),
('10.0.3.18', 'app', 'order-service', 'sec-review-2026-09'),
('10.0.8.2', 'ops_admin', 'pgadmin', 'sec-review-2026-09');-- 每日核对:采集端按天汇总的 (host, user, app) 组合与基线做差
-- (audit_conn_daily 为日志平台侧回灌的每日汇总表)
SELECT d.remote_host, d.usename, d.app_name, d.conns
FROM audit_conn_daily d
LEFT JOIN conn_baseline b
ON b.remote_host = d.remote_host
AND b.usename = d.usename
AND b.app_name = d.app_name
WHERE d.day = current_date - 1
AND b.remote_host IS NULL
ORDER BY d.conns DESC;基线外组合按约定处置:业务负责人认领 → 补基线;无人认领 → 按事件流程核查。基线表本身纳入变更管理,每次增删都记录审批人与原因。
注意事项
这两个参数不能在会话里临时打开。 log_connections / log_disconnections 只能在配置文件(或启动命令行)中设置,reload 后对新建连接生效,已存在的会话不受影响。想「临时记录某个会话的连接信息」是不成立的,规划时按常态开启考虑。
connection authorized 的 application_name 是客户端自报的。 任何客户端都可以把 application_name 设成任何值,它有辅助归因价值但没有身份证明效力。审计结论要以账号 + 来源 IP + 语句内容为主,application_name 只作旁证。
连接池会稀释来源信息。 见前文:池化架构下日志里的 host 是池子主机、user 是池子的服务端账号。多租户池化环境中,把数据库侧连接审计与池子日志、应用层审计拼起来才有完整链路。
失败认证日志永远在,成功连接日志才需要开关。 这意味着即使什么都不配置,日志里也会持续出现失败尝试(公网暴露的实例尤其多)。给日志配额做预算时不要按「全关」假设,要用实测值。
日志量与连接频率成正比、与流量无关。 短连接风暴(每秒数百次建连)会把连接日志放大成主要噪音,这种场景优先治理应用连接方式(上池子),而不是关掉连接审计。
%q 哨兵要写进 log_line_prefix。 没有 %q 时,connection received 这类发生在认证前的日志行会因为取不到用户/库名而输出空字段或丑陋的空格段;写上 %q(如 %m [%p] %q%u@%d )让无会话上下文的日志自动省略这些字段。
集中采集不能替代本地留存。 采集链路可能中断、可能被绕过。本地日志保留期应满足合规最低要求,采集平台上的留存期可以更长;两者都要有访问控制——日志中含 IP、账号与业务数据影子。
sessions 统计是 PG14+ 的列。 低版本实例上 pg_stat_database 没有这些列,异常终止趋势只能依赖日志统计。升级规划时可以把这一列列为监控便利性的加分项。
log_hostname 会引入 DNS 依赖。 开启后每个连接都要做反向解析,DNS 抖动会拖慢建连;保持默认 off、用 IP 归因,主机名由资产管理系统映射。
认证失败的 pg_hba 匹配详情属于调试信息。 「失败对应哪一行规则」在调试级别日志中才可见,日常不要为此调低阈值;需要复盘时在测试环境复现。
来源 IP 合法不等于会话可信。 网段内任何主机都可能冒用同一账号——IP 是低置信度信号,高危操作仍需权限与语句审计双重约束。
连接日志不回答「为什么断开」。 disconnection 行给出会话时长,但断开原因(客户端主动、网络中断、超时参数触发)要结合同时段 FATAL / ERROR 行与时间轴判断,不要把每条断开都当异常。
「数据库在记」与「平台在收」要分别验证。 采集管道中断不会影响本地日志,却会让平台侧的连接审计静默失明;定期做端到端探针(建一条测试连接,确认日志平台可检索到对应行),连接审计的存活监控才算闭环。
最佳实践
- 连接日志默认全程开启。 它的日志量小(每连接三行)、与业务流量无关、又是最难伪造的审计证据,是所有挡位里最没有理由关闭的一类。
log_line_prefix固定为%m [%p] %q%u@%d,接入集中日志的实例再加%h、%c、%i。PID 与会话 ID 是串联证据链的钥匙,缺失后跨行关联基本靠猜。- 把「连接来源基线」变成资产。 用采集平台维护一张「合法来源 × 账号 × 应用」的基线表,新出现组合触发告警——比事后翻日志有效得多。
- 对失败认证设置阈值告警。 单一来源对单一账号的失败次数超过阈值(如 10 次/分钟)即告警并考虑自动封禁;
pg_hba.conf遵循最小网段原则,为爆破提供的第一道拦截。 - 为运维账号与业务账号设定不同的会话画像。 运维账号应为「短时长、低频、来自跳板机」;业务账号应为「长连接、来自固定网段、application_name 稳定」。偏离画像的会话进入待核实清单。
- 用
pg_stat_database的会话计数器做趋势监控,用日志做个案回溯。计数器便宜且适合做每小时快照,日志昂贵但细节完整,先计数器后日志。 - PG15+ 优先启用
jsonlog。 结构化日志让 ELK / Loki 的采集解析几乎零成本,连接、语句、错误三类日志可以共用同一条管道与同一套索引。 - 池化架构把连接审计重心上移。 数据库侧看「池子 ↔ 库」,池子日志看「客户端 ↔ 池子」,应用层看「用户 ↔ 应用」;三层日志用 request id / 会话时间轴对齐,缺一层就补一层。
- 定期用连接日志驱动账号清理。 每季度导出「账号最后连接时间」清单,超过阈值未使用的账号进入回收流程;离职账号的回收验证也以此为准。
- 连接审计与语句审计联动设计挡位。 连接日志负责「人」,语句日志负责「行为」;运维角色两者都开,业务角色连接必开、语句按挡位。不要出现「有连接没行为」或「有行为没连接」的半截证据链。
- 把连接日志纳入事件响应预案。 预置好「按 PID 提取会话全部日志」「按来源 IP 聚合当日连接」这类脚本(见实战示例二/三),事发时的响应速度以分钟计。
下一节
继续学习:pgaudit 扩展
参考链接
- PostgreSQL 官方文档:When to Log(log_connections / log_disconnections)
- PostgreSQL 官方文档:Error Reporting and Logging
- PostgreSQL 官方文档:Connections and Authentication(连接相关配置)
- PostgreSQL 官方文档:The pg_hba.conf File
- PostgreSQL 官方文档:pg_stat_database(sessions 等统计列)
- PostgreSQL 官方文档:pg_stat_activity
- PostgreSQL 官方文档:Log File Maintenance
- PgBouncer 官方站点
- Elastic Stack(ELK)概述
- Grafana Loki 官方文档