Discovering and Interpreting API Errors in the Logs
A complimentary guide was made for the Postgres logs
Navigating the API logs:#
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 requests are the rows where source = 'edge_logs'.
Notably, it contains:
| field | description |
|---|---|
| event_message | the log's message |
| timestamp | time event was recorded |
| log_attributes | structured request and response fields, keyed by dotted path |
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.
Field access example
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_codefrom logswhere source = 'edge_logs'limit 100;The most useful fields for debugging are:
NOTE: not every field is included below. For a full list, check the API Edge field reference
Request object#
Cloudflare geographic data:#
Suggested use cases:
- Detecting abuse from a specific region
- Detecting activity spikes from certain regions
| Column | Description | Sample value |
|---|---|---|
| request.cf.city | Requester's city | Munich |
| request.cf.country | Requester's country | DE |
| request.cf.continent | Requester's continent | EU |
| request.cf.region | Requester's region | Bavaria |
| request.cf.latitudex | Requester's latitude | 48.10840 |
| request.cf.longitude | Requester's longitude | 11.61020 |
| request.cf.timezone | Requester's timezone | Europe/Berlin |
Unnesting example:
select log_attributes['request.cf.city'] as cityfrom logswhere source = 'edge_logs'limit 100;IP and browser/environment data:#
Suggested use cases:
- Detecting request behavior from IP
- Detecting abuse by IP
- Detecting errors by user_agent
| Column | Description | Sample value |
|---|---|---|
| request.headers.cf_connecting_ip | Requester's IP | 80.81.18.138 |
| request.headers.user_agent | Requester's browser or app environment | Mozilla/5.0 (Linux; Android 11; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36 |
Unnesting example:
select log_attributes['request.headers.cf_connecting_ip'] as cf_connecting_ipfrom logswhere source = 'edge_logs'limit 100;Query type and formatting data:#
Suggested use cases:
- identify problematic queries
- identify unusual behavior by authenticated users
| Column | Description | Sample value |
|---|---|---|
| request.method | Request Method (PATCH, GET, PUT...) | GET |
| request.url | Request URL, which contains the PostgREST formatted query | https://yuhplfrsdxxxtldakizi.supabase.co/rest/v1/users?select=username&id=eq.63b6190e-214f-4b8a-b72d-3af6e1921411&limit=1 |
| request.sb.jwt.authorization.payload.subject | authenticated user's ID | 63b6190e-214f-4b8a-b72d-3af6e1921411 |
Unnesting example:
select log_attributes['request.method'] as method, log_attributes['request.url'] as url, log_attributes['request.sb.jwt.authorization.payload.subject'] as auth_userfrom logswhere source = 'edge_logs'limit 100;Response object#
Status code:#
Suggested use cases:
- detect success/errors
| Column | Description | Sample value |
|---|---|---|
| response.status_code | Response status code (200, 404, 500...) | 404 |
Unnesting example:
select log_attributes['response.status_code'] as status_codefrom logswhere source = 'edge_logs'limit 100;Finding errors#
API level errors#
The metadata.request.url contains PostgREST formatted queries.
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=nameYou can use regex (Advanced Regex Guide) to find the objects related to your query. Try isolating by:
- function names
- column names
- table names
- query methods (select, insert, ...)
Example:
select timestamp, log_attributes['response.status_code'] as status_code, log_attributes['request.url'] as url, event_messagefrom logswhere 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 desclimit 100;PostgREST has an error reference table that you can use to interpret status codes.
Database-level errors#
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_messagefrom logswhere 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 desclimit 100;Like PostgREST, Postgres has a reference table for interpreting error codes.
PostgREST server and Cloudflare errors#
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:#
Find All Errors:
select timestamp, log_attributes['response.status_code'] as status_code, event_message, log_attributes['request.path'] as pathfrom logswhere 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 desclimit 100;Group errors by path and code:
select log_attributes['response.status_code'] as status_code, log_attributes['request.path'] as path, count() as reoccurrence_per_pathfrom logswhere 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 APIgroup by path, status_codeorder by reoccurrence_per_path desclimit 100;Find requests by region:
select log_attributes['request.path'] as path, log_attributes['request.cf.region'] as region, count() as region_countfrom logswhere source = 'edge_logs' -- only look at DB API and match(log_attributes['request.path'], '^/rest/v1/')group by region, pathorder by region_count desclimit 100;Find total requests by IP:
select log_attributes['request.headers.cf_connecting_ip'] as ip, count() as ip_countfrom logswhere source = 'edge_logs' and match(log_attributes['request.path'], '^/auth/v1/')group by iporder by ip_count desclimit 100;Search frequented query paths by authenticated user:
select -- only available for front-end clients log_attributes['request.sb.jwt.authorization.payload.subject'] as auth_user, log_attributes['request.path'] as path, count() as request_countfrom logswhere source = 'edge_logs' -- only look at DB API and match(log_attributes['request.path'], '^/rest/v1/')group by auth_user, pathorder by request_count desclimit 100;