Discovering and Interpreting API Errors in the Logs

Last edited: 1/18/2025

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 Log Explorer. These logs are managed through Logflare and can be queried with a subset of BigQuery SQL syntax.

The log table that contains API requests is edge_logs.

Notably, it contains:

fielddescription
event_messagethe log's message
timestamptime event was recorded
request metadatametadata about the REST request
response metadatametadata about the REST response

The request and response columns are arrays in the metadata field and must be unnested. This is done with a cross join.

Unnesting example


_11
select
_11
-- the event message does not require unnesting
_11
event_message,
_11
-- unnested status_code column from metadata.response field
_11
status_code
_11
from
_11
edge_logs
_11
-- Unpack data stored in the 'metadata' field
_11
cross join unnest(metadata) as metadata
_11
-- After unpacking the 'metadata' field, extract the 'response' field from it
_11
cross join unnest(response) as response;

The most useful fields for debugging are:

NOTE: not every field is included below. For a full list, check the API Edge field reference in the Log Explorer

Request object

Cloudflare geographic data:

Suggested use cases:

  • Detecting abuse from a specific region
  • Detecting activity spikes from certain regions
ColumnDescriptionSample value
request.cf.cityRequester's cityMunich
request.cf.countryRequester's countryDE
request.cf.continentRequester's continentEU
request.cf.regionRequester's regionBavaria
request.cf.latitudexRequester's latitude48.10840
request.cf.longitudeRequester's longitude11.61020
request.cf.timezoneRequester's timezoneEurope/Berlin

Unnesting example:


_10
select
_10
city
_10
from
_10
edge_logs
_10
-- Unpack 'metadata' field
_10
cross join unnest(metadata) AS metadata
_10
-- unpack 'request' from 'metadata'
_10
cross join unnest(request) AS request;
_10
-- unpack 'cf' from 'request'
_10
cross join unnest(cf) AS cf;

IP and browser/environment data:

Suggested use cases:

  • Detecting request behavior from IP
  • Detecting abuse by IP
  • Detecting errors by user_agent
ColumnDescriptionSample value
request.headers.cf_connecting_ipRequester's IP80.81.18.138
request.headers.user_agentRequester's browser or app environmentMozilla/5.0 (Linux; Android 11; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36

Unnesting example:


_10
select
_10
cf_connecting_ip
_10
from
_10
edge_logs
_10
-- Unpack 'metadata' field
_10
cross join unnest(metadata) AS metadata
_10
-- unpack 'request' from 'metadata'
_10
cross join unnest(request) AS request;
_10
-- unpack 'headers' from 'request'
_10
cross join unnest(headers) AS headers;

Query type and formatting data:

Suggested use cases:

  • identify problematic queries
  • identify unusual behavior by authenticated users
ColumnDescriptionSample value
request.methodRequest Method (PATCH, GET, PUT...)GET
request.urlRequest URL, which contains the PostgREST formatted queryhttps://yuhplfrsdxxxtldakizi.supabase.co/rest/v1/users?select=username&id=eq.63b6190e-214f-4b8a-b72d-3af6e1921411&limit=1
request.sb.auth_usersauthenticated user's ID63b6190e-214f-4b8a-b72d-3af6e1921411

Unnesting example:


_12
select
_12
method,
_12
url,
_12
auth_users
_12
from
_12
edge_logs
_12
-- Unpack 'metadata' field
_12
cross join unnest(metadata) AS metadata
_12
-- unpack 'request' from 'metadata'
_12
cross join unnest(request) AS request;
_12
-- unpack 'sb' from 'request'
_12
cross join unnest(sb) AS sb;

Response object

Status code:

Suggested use cases:

  • detect success/errors
ColumnDescriptionSample value
response.status_codeResponse status code (200, 404, 500...)404

Unnesting example:


_10
select
_10
status_code
_10
from
_10
edge_logs
_10
-- Unpack 'metadata' field
_10
cross join unnest(metadata) as metadata
_10
-- unpack 'response' from 'metadata'
_10
cross join unnest(response) as response;

Finding errors

API level errors

The metadata.request.url contains PostgREST formatted queries.

For example, the following call to the JS client:


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

translates to calling the following endpoint:


_10
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:

  • function names
  • column names
  • table names
  • query methods (select, insert, ...)

Example:


_19
select
_19
cast(timestamp as datetime) as timestamp,
_19
status_code,
_19
url,
_19
event_message
_19
from edge_logs
_19
cross join unnest(metadata) as metadata
_19
cross join unnest(response) AS request;
_19
cross join unnest(response) AS response;
_19
where
_19
-- find all errors
_19
status_code >= 400
_19
and
_19
-- find queries featuring the a specific <table_name> and <column_name>
_19
(
_19
regexp_contains(url, '<table_name>')
_19
and
_19
regexp_contains(event_message, '<column_name1>|<column_name2>')
_19
)

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 PostgreSQL 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.


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

Like PostgREST, PostgreSQL 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:


_15
select
_15
cast(timestamp as datetime) as timestamp,
_15
status_code,
_15
event_message,
_15
path
_15
from
_15
edge_logs
_15
cross join unnest(metadata) as metadata
_15
cross join unnest(response) as response
_15
cross join unnest(request) as request
_15
where
_15
-- find all errors
_15
status_code >= 400
_15
and regexp_contains(path, '^/rest/v1/');
_15
-- only look at DB API

Group errors by path and code:


_15
select
_15
status_code,
_15
path,
_15
count(path) as reoccurrence_per_path
_15
from
_15
edge_logs
_15
cross join unnest(metadata) as metadata
_15
cross join unnest(response) as response
_15
cross join unnest(request) as request
_15
where
_15
-- find all errors
_15
status_code >= 400
_15
and regexp_contains(path, '^/rest/v1/') -- only look at DB API
_15
group by path, status_code
_15
order by reoccurrence_per_path;

Find requests by region:


_14
select
_14
path,
_14
region,
_14
count(region) as region_count
_14
from
_14
edge_logs
_14
cross join unnest(metadata) as metadata
_14
cross join unnest(request) as request
_14
cross join unnest(cf) as cf
_14
where
_14
-- only look at DB API
_14
regexp_contains(path, '^/rest/v1/')
_14
group by region, path
_14
order by requester_region_count;

Find total requests by IP:


_13
select
_13
cf_connecting_ip as ip,
_13
count(cf_connecting_ip) as ip_count
_13
from
_13
edge_logs
_13
cross join unnest(metadata) as metadata
_13
cross join unnest(request) as request
_13
cross join unnest(headers) as headers
_13
cross join unnest(cf) as cf
_13
cross join unnest(response) as response
_13
where regexp_contains(path, '^/auth/v1/')
_13
group by ip
_13
order by ip_count;

Search frequented query paths by authenticated user:


_14
select
_14
-- only available for front-end clients
_14
auth_users,
_14
path,
_14
count(auth_users) as ip_count
_14
from
_14
edge_logs
_14
cross join unnest(metadata) as metadata
_14
cross join unnest(request) as request
_14
cross join unnest(sb) as sb
_14
where
_14
-- only look at DB API
_14
regexp_contains(path, '^/rest/v1/')
_14
group by auth_users, path;