Skip to content

Discovering and Interpreting API Errors in the Logs

为 Postgres 日志制作了一个免费的指南

🌐 Navigating the API logs:

数据库 API 由 PostgREST 网页服务器 提供支持,每个请求都会记录到 API Edge 网络日志中。要精确浏览它们,可以使用 SQL 编辑器,并将查询源设置为 Logs。这些日志运行在 ClickHouse 上。每条日志都是单个 logs 表中的一行,通过 source 列进行标记。

🌐 The Database API is powered by a PostgREST web-server, recording every request to the API Edge Network logs. To precisely navigate them, use the SQL Editor with the query source set to Logs. These logs run on ClickHouse. Every log line is a row in a single logs table, tagged by a source column.

API 请求就是那些 source = 'edge_logs' 的行。

🌐 API requests are the rows where source = 'edge_logs'.

值得注意的是,它包含:

字段描述
event_message日志的消息
timestamp事件记录的时间
log_attributes结构化的请求和响应字段,用点路径作为键

🌐 Notably, it contains:

fielddescription
event_messagethe log's message
timestamptime event was recorded
log_attributesstructured request and response fields, keyed by dotted path

请求和响应的详细信息保存在 log_attributes 映射中。使用方括号访问字段,同时保留完整的点分键。没有任何非嵌套的联合操作。

🌐 Request and response details live in the log_attributes map. Read a field with bracket access, keeping the full dotted key. There are no unnesting joins.

字段访问示例

select
-- event_message is a column, so it needs no lookup
event_message,
-- response.status_code is a log_attributes key
log_attributes['response.status_code'] as status_code
from logs
where source = 'edge_logs'
limit 100;

最有用来调试的字段有:

🌐 The most useful fields for debugging are:

注意:下面并未包含所有字段。要查看完整列表,请查阅 API Gateway 和 Function Edge 的 日志字段参考。

请求对象 #

🌐 Request object

Cloudflare 地理数据: #

🌐 Cloudflare geographic data:

建议使用场景:

  • 检测来自特定地区的滥用行为
  • 检测某些区域的活动高峰
列描述示例值
request.cf.city请求者的城市慕尼黑
request.cf.country请求者的国家德国
request.cf.continent请求者的洲欧洲
request.cf.region请求者的地区巴伐利亚
request.cf.latitudex请求者的纬度48.10840
request.cf.longitude请求者的经度11.61020
request.cf.timezone请求者的时区欧洲/柏林

展开示例:

select
log_attributes['request.cf.city'] as city
from logs
where source = 'edge_logs'
limit 100;

IP 和浏览器/环境数据: #

🌐 IP and browser/environment data:

建议使用场景:

  • 从IP检测请求行为
  • 通过IP检测滥用
  • 通过用户代理检测错误
列描述示例值
request.headers.cf_connecting_ip请求者的 IP80.81.18.138
request.headers.user_agent请求者的浏览器或应用环境Mozilla/5.0 (Linux; Android 11; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36

展开示例:

select
log_attributes['request.headers.cf_connecting_ip'] as cf_connecting_ip
from logs
where source = 'edge_logs'
limit 100;

查询类型和格式化数据: #

🌐 Query type and formatting data:

建议使用场景:

  • 找出有问题的查询
列描述示例值
request.method请求方法(PATCH, GET, PUT...)GET
request.url请求 URL,包含 PostgREST 格式的查询https://yuhplfrsdxxxtldakizi.supabase.co/rest/v1/users?select=username&id=eq.63b6190e-214f-4b8a-b72d-3af6e1921411&limit=1

展开示例:

select
log_attributes['request.method'] as method,
log_attributes['request.url'] as url
from logs
where source = 'edge_logs'
limit 100;

响应对象 #

🌐 Response object

状态码: #

🌐 Status code:

建议使用场景:

  • 检测成功/错误
列描述示例值
response.status_code响应状态码 (200, 404, 500...)404

展开示例:

select
log_attributes['response.status_code'] as status_code
from logs
where source = 'edge_logs'
limit 100;

查找错误 #

🌐 Finding errors

API级别错误 #

🌐 API level errors

metadata.request.url 包含 PostgREST 格式的查询。

🌐 The metadata.request.url contains PostgREST formatted queries.

例如,下面对 JS 客户端的调用:

🌐 For example, the following call to the JS client:

let { data: countries, error } = await supabase.from('countries').select('name')

对应调用以下端点:

🌐 translates to calling the following endpoint:

https://<project ref>.supabase.co/rest/v1/countries?select=name

你可以使用正则表达式 (高级正则指南) 来查找与你的查询相关的对象。尝试按以下方式隔离:

🌐 You can use regex (Advanced Regex Guide) to find the objects related to your query. Try isolating by:

  • 函数名
  • 列名
  • 表名
  • 查询方法(选择、插入、...)

示例:

🌐 Example:

select
timestamp,
log_attributes['response.status_code'] as status_code,
log_attributes['request.url'] as url,
event_message
from logs
where
source = 'edge_logs'
-- find all errors
and toInt32OrZero(log_attributes['response.status_code']) >= 400
-- find queries featuring a specific <table_name> and <column_name>
and match(log_attributes['request.url'], '<table_name>')
and match(event_message, '<column_name1>|<column_name2>')
order by timestamp desc
limit 100;

PostgREST 有一个 错误参考表,你可以用它来解读状态码。

🌐 PostgREST has an error reference table that you can use to interpret status codes.

数据库级错误 #

🌐 Database-level errors

不过,通过数据库 API 报告的一些错误是在 Postgres 层发生的。如果不清楚发生了哪种错误,你应该参考错误的时间戳,并尝试查看是否可以在 Postgres 日志中找到它。

🌐 However, some errors that are reported through the Database API occur at the Postgres level. If it is not clear which error occurred you should reference the timestamp of the error and try to see if you can find it in the Postgres logs.

select
timestamp,
log_attributes['parsed.error_severity'] as error_severity,
log_attributes['parsed.user_name'] as user_name,
log_attributes['parsed.query'] as query,
log_attributes['parsed.detail'] as detail,
log_attributes['parsed.sql_state_code'] as sql_state_code,
event_message
from logs
where
source = 'postgres_logs'
-- filter only for error events
and log_attributes['parsed.error_severity'] in ('ERROR', 'FATAL', 'PANIC')
-- All DB API requests are registered as the authenticator role
and log_attributes['parsed.user_name'] = 'authenticator'
-- find failed queries featuring the function <function_name>
and match(log_attributes['parsed.query'], '<function_name>')
-- limit the time of the search to be around the time of the failed API request
and timestamp between '2024-04-15 10:50:00' and '2024-04-15 10:50:27'
order by timestamp desc
limit 100;

像 PostgREST 一样,Postgres 也有一个参考表用来解释错误代码。

🌐 Like PostgREST, Postgres has a reference table for interpreting error codes.

PostgREST 服务器和 Cloudflare 错误 #

🌐 PostgREST server and Cloudflare errors

在某些情况下,错误可能是由于 Cloudflare 或 PostgREST 服务器错误引起的。对于 500 及以上的错误,你可能需要检查你的 PostgREST 日志以及 Cloudflare 文档。

🌐 In some cases, errors may emerge because of Cloudflare or PostgREST server errors. For 500 and above errors, you may want to check your PostgREST logs and the Cloudflare docs.)

实际例子: #

🌐 Practical examples:

查找所有错误:

select
timestamp,
log_attributes['response.status_code'] as status_code,
event_message,
log_attributes['request.path'] as path
from logs
where
source = 'edge_logs'
-- find all errors
and toInt32OrZero(log_attributes['response.status_code']) >= 400
-- only look at DB API
and match(log_attributes['request.path'], '^/rest/v1/')
order by timestamp desc
limit 100;

按路径和代码分组错误:

select
log_attributes['response.status_code'] as status_code,
log_attributes['request.path'] as path,
count() as reoccurrence_per_path
from logs
where
source = 'edge_logs'
-- find all errors
and toInt32OrZero(log_attributes['response.status_code']) >= 400
and match(log_attributes['request.path'], '^/rest/v1/') -- only look at DB API
group by path, status_code
order by reoccurrence_per_path desc
limit 100;

按地区查找请求:

select
log_attributes['request.path'] as path,
log_attributes['request.cf.region'] as region,
count() as region_count
from logs
where
source = 'edge_logs'
-- only look at DB API
and match(log_attributes['request.path'], '^/rest/v1/')
group by region, path
order by region_count desc
limit 100;

按IP统计总请求数:

select
log_attributes['request.headers.cf_connecting_ip'] as ip,
count() as ip_count
from logs
where
source = 'edge_logs'
and match(log_attributes['request.path'], '^/auth/v1/')
group by ip
order by ip_count desc
limit 100;