Supabase 日志查询迁到统一 logs 表:这套 ClickHouse SQL 可以直接抄

2026-09-21 Supabase日志ClickHouse运维

Supabase 的日志查询换了一套查询面之后,最难受的不是”要改”,而是”改完返回空,也不知道是没数据还是写错了”。这篇把能直接复制的查询和验收办法放在一处。

以下写法取自 Supabase 官方的日志查询文档与 Management API 示例。

先记住三条会咬人的规则

第一,来源字段是 source,不是 source_name。 7 月 23 日那份迁移公告的示例代码写的是 WHERE source_name = 'edge_logs',但官方现行文档里的示例与实际查询面用的是 source。照抄公告不会报错,只会静默返回空结果——你会以为那天没有日志。

第二,log_attributes 里的值全是字符串。 想比较数字必须先转:

toInt32OrZero(log_attributes['response.status_code']) between 500 and 599

toInt32OrZero 会把缺失值和非数字值都变成 0,所以别把这个 0 当成真实的响应码或耗时。取字段时保留完整的点分路径,不要自己拆层。

第三,select * 和 count(*) 会被拒绝。 查询面要求列出具体字段,聚合要用 count()。同时必须给一个带边界的时间范围,API 单次窗口不超过 24 小时。

四条可以直接抄的查询

1. 最近的 API 网关 5xx

select timestamp, id,
  toInt32OrZero(log_attributes['response.status_code']) as status,
  log_attributes['request.path'] as path
from logs
where source = 'edge_logs'
  and toInt32OrZero(log_attributes['response.status_code']) between 500 and 599
order by timestamp desc
limit 100;

2. 连接失败类错误

select timestamp, id, event_message
from logs
where source = 'postgres_logs'
  and event_message ilike '%connection%'
  and match(event_message, '(?i)failed|refused|timeout')
order by timestamp desc
limit 100;

3. 不知道有哪些字段可用时,先把 key 摸出来

select arrayJoin(mapKeys(log_attributes)) as key, count() as events
from logs
where source = 'postgres_logs'
group by key
order by events desc
limit 100;

这条应该在迁移任何一条旧查询之前先跑一遍,比你猜字段名快。

4. 走 Management API

把上面的 SQL 存成 logs.sql:

curl --get "https://api.supabase.com/v1/projects/$PROJECT_REF/analytics/endpoints/logs" \
  --header "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  --data-urlencode "[email protected]" \
  --data-urlencode "iso_timestamp_start=$START" \
  --data-urlencode "iso_timestamp_end=$END"

$START 与 $END 用 UTC 时间戳,跨度不超过 24 小时。不带 sql 参数时,这个端点只返回 API Gateway 的事件——如果你发现查出来全是网关日志,先确认自己传了 sql。

迁移自查清单

  • 端点路径从 analytics/endpoints/logs.all 改成 analytics/endpoints/logs
  • 源表(如 FROM edge_logs)改成 FROM logs + WHERE source = '...'
  • 所有 CROSS JOIN unnest(metadata) 的逐层展开,换成 log_attributes['a.b.c']
  • 数值比较外面套 toInt32OrZero()
  • 去掉 select * 和 count(*)
  • 脚本里显式传 iso_timestamp_start / iso_timestamp_end,别指望服务端替你兜
  • 用 MCP 的,把 mcp-server-supabase 升到 v0.10.0,并改用 query_logs(execute_sql 是查 Postgres 的,不查 ClickHouse 日志)
  • Supabase CLI 查不了 ClickHouse 日志,只能用上面的 Management API

验收标准:怎么证明自己迁对了

别用”跑通了”当验收。三条硬标准:

  1. 非空校验:同一个时间窗、同一个源,新查询返回的行数不为 0。是 0 就先怀疑字段名,不要下”那段时间没日志”的结论。
  2. 对照校验:挑一个已知会出现的事件(比如你手动打一次 404),新查询必须能在 5 分钟内查到它。查不到说明 source 或 log_attributes 的路径写错了。
  3. 计数校验:把旧查询在还能用的时候跑一次记下行数,迁移后同一窗口的行数应在同一量级。差一个数量级基本就是 source 写成了 source_name 或者漏了时间范围。

三条都过才算迁完。只过第一条,是最容易自欺的那种”好了”。

什么情况别折腾

  • 你只在 Dashboard 的 Logs Explorer 里看日志——那不受影响,什么都不用改。
  • 你的日志消费全靠第三方可观测平台接的 Postgres 或 webhook,没直接调 logs.all——同样不用改。
  • 你的脚本一年跑不了两次、坏了有人肉眼看——那就等它真报错了再改,别为它排期。

参考:https://supabase.com/docs/guides/observability/advanced-log-filtering