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
1select2 -- event_message is a column, so it needs no lookup3 event_message,4 -- response.status_code is a log_attributes key5 log_attributes['response.status_code'] as status_code6from logs7where source = 'edge_logs'8limit 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:
1select2 log_attributes['request.cf.city'] as city3from logs4where source = 'edge_logs'5limit 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:
1select2 log_attributes['request.headers.cf_connecting_ip'] as cf_connecting_ip3from logs4where source = 'edge_logs'5limit 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:
1select2 log_attributes['request.method'] as method,3 log_attributes['request.url'] as url,4 log_attributes['request.sb.jwt.authorization.payload.subject'] as auth_user5from logs6where source = 'edge_logs'7limit 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:
1select2 log_attributes['response.status_code'] as status_code3from logs4where source = 'edge_logs'5limit 100;Finding errors#
API level errors#
The metadata.request.url contains PostgREST formatted queries.
For example, the following call to the JS client:
1let { data: countries, error } = await supabase.from('countries').select('name')translates to calling the following endpoint:
1https://<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:
1select2 timestamp,3 log_attributes['response.status_code'] as status_code,4 log_attributes['request.url'] as url,5 event_message6from logs7where8 source = 'edge_logs'9 -- find all errors10 and toInt32OrZero(log_attributes['response.status_code']) >= 40011 -- find queries featuring a specific <table_name> and <column_name>12 and match(log_attributes['request.url'], '<table_name>')13 and match(event_message, '<column_name1>|<column_name2>')14order by timestamp desc15limit 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.
1select2 timestamp,3 log_attributes['parsed.error_severity'] as error_severity,4 log_attributes['parsed.user_name'] as user_name,5 log_attributes['parsed.query'] as query,6 log_attributes['parsed.detail'] as detail,7 log_attributes['parsed.sql_state_code'] as sql_state_code,8 event_message9from logs10where11 source = 'postgres_logs'12 -- filter only for error events13 and log_attributes['parsed.error_severity'] in ('ERROR', 'FATAL', 'PANIC')14 -- All DB API requests are registered as the authenticator role15 and log_attributes['parsed.user_name'] = 'authenticator'16 -- find failed queries featuring the function <function_name>17 and match(log_attributes['parsed.query'], '<function_name>')18 -- limit the time of the search to be around the time of the failed API request19 and timestamp between '2024-04-15 10:50:00' and '2024-04-15 10:50:27'20order by timestamp desc21limit 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:
1select2 timestamp,3 log_attributes['response.status_code'] as status_code,4 event_message,5 log_attributes['request.path'] as path6from logs7where8 source = 'edge_logs'9 -- find all errors10 and toInt32OrZero(log_attributes['response.status_code']) >= 40011 -- only look at DB API12 and match(log_attributes['request.path'], '^/rest/v1/')13order by timestamp desc14limit 100;Group errors by path and code:
1select2 log_attributes['response.status_code'] as status_code,3 log_attributes['request.path'] as path,4 count() as reoccurrence_per_path5from logs6where7 source = 'edge_logs'8 -- find all errors9 and toInt32OrZero(log_attributes['response.status_code']) >= 40010 and match(log_attributes['request.path'], '^/rest/v1/') -- only look at DB API11group by path, status_code12order by reoccurrence_per_path desc13limit 100;Find requests by region:
1select2 log_attributes['request.path'] as path,3 log_attributes['request.cf.region'] as region,4 count() as region_count5from logs6where7 source = 'edge_logs'8 -- only look at DB API9 and match(log_attributes['request.path'], '^/rest/v1/')10group by region, path11order by region_count desc12limit 100;Find total requests by IP:
1select2 log_attributes['request.headers.cf_connecting_ip'] as ip,3 count() as ip_count4from logs5where6 source = 'edge_logs'7 and match(log_attributes['request.path'], '^/auth/v1/')8group by ip9order by ip_count desc10limit 100;Search frequented query paths by authenticated user:
1select2 -- only available for front-end clients3 log_attributes['request.sb.jwt.authorization.payload.subject'] as auth_user,4 log_attributes['request.path'] as path,5 count() as request_count6from logs7where8 source = 'edge_logs'9 -- only look at DB API10 and match(log_attributes['request.path'], '^/rest/v1/')11group by auth_user, path12order by request_count desc13limit 100;