Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/docs/app/guides/troubleshooting/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { notFound } from 'next/navigation'

import TroubleshootingPage from '~/features/docs/Troubleshooting.page'
import { getAllTroubleshootingEntries, getArticleSlug } from '~/features/docs/Troubleshooting.utils'
import { PROD_URL } from '~/lib/constants'
import { getCustomContent } from '~/lib/custom-content/getCustomContent'
import { mdAlternate } from '~/lib/md-alternates'
import { notFound } from 'next/navigation'

export const dynamicParams = false

Expand Down Expand Up @@ -38,6 +38,7 @@ export const generateMetadata = async (props: { params: Promise<{ slug: string }
title: `${metadataTitle || 'Supabase'} | Troubleshooting${entry ? ` | ${entry.data.title}` : ''}`,
alternates: {
canonical: `${PROD_URL}/guides/troubleshooting/${slug}`,
types: mdAlternate(`troubleshooting/${slug}`),
},
}
}
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/app/guides/troubleshooting/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TROUBLESHOOTING_CONTAINER_ID } from '~/features/docs/Troubleshooting.ut
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
import { PROD_URL } from '~/lib/constants'
import { getCustomContent } from '~/lib/custom-content/getCustomContent'
import { mdAlternate } from '~/lib/md-alternates'
import { type Metadata } from 'next'

const { metadataTitle } = getCustomContent(['metadata:title'])
Expand Down Expand Up @@ -62,5 +63,6 @@ export const metadata: Metadata = {
title: `${metadataTitle || 'Supabase'} | Troubleshooting`,
alternates: {
canonical: `${PROD_URL}/guides/troubleshooting`,
types: mdAlternate('troubleshooting'),
},
}
5 changes: 1 addition & 4 deletions apps/docs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ const generateMetadata = async (_, parent: ResolvingMetadata): Promise<Metadata>
...(parentAlternates && {
languages: parentAlternates.languages || undefined,
media: parentAlternates.media || undefined,
types: {
...(parentAlternates.types ?? {}),
'text/markdown': 'https://supabase.com/llms-full.txt',
},
types: parentAlternates.types || undefined,
}),
},
}
Expand Down
10 changes: 9 additions & 1 deletion apps/docs/components/StepHikeCompact/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,18 @@ const Details: FC<PropsWithChildren<IDetails>> = ({ children, title, fullWidth =
}

const Code: FC<PropsWithChildren<ICode>> = ({ children }) => {
// Not `not-prose`: steps interleave labels and admonitions with their code samples, and
// stripping prose leaves that text unstyled and flush against the samples.
return (
<div
data-step-hike="code"
className="not-prose min-w-0 w-full [&_.shiki]:!my-0 [&_.shiki-wrapper]:!my-0"
className={cn(
'min-w-0 w-full',
// `Step` spaces the block as a whole, so samples don't carry margins of their own...
'[&_.shiki]:!my-0 [&_.shiki-wrapper]:!my-0',
// ...but back-to-back samples have no prose between them to separate them.
'[&_.shiki+.shiki]:!mt-6'
)}
>
{children}
</div>
Expand Down
156 changes: 70 additions & 86 deletions apps/docs/content/guides/api/rest/postgrest-error-codes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,28 +145,26 @@ Data API error unspecified

## Viewing errors in the logs

One can filter for API errors in the [log explorer](/dashboard/project/_/logs/explorer). Below are useful queries for filtering and analyzing API errors:
One can filter for API errors in the [SQL Editor](/dashboard/project/_/sql/new?skip=true&source=logs) with the query source set to **Logs**. Below are useful queries for filtering and analyzing API errors:

### Find all API errors that occurred at the database level

```sql
select
cast(postgres_logs.timestamp as datetime) as timestamp,
timestamp,
event_message,
parsed.error_severity,
parsed.user_name,
parsed.query,
parsed.detail,
parsed.hint,
parsed.sql_state_code,
parsed.backend_type
from
postgres_logs
cross join unnest(metadata) as metadata
cross join unnest(metadata.parsed) as parsed
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.hint'] as hint,
log_attributes['parsed.sql_state_code'] as sql_state_code,
log_attributes['parsed.backend_type'] as backend_type
from logs
where
regexp_contains(parsed.error_severity, 'ERROR|FATAL|PANIC')
and parsed.user_name = 'authenticator' -- the authenticator role represents the database API
source = 'postgres_logs'
and log_attributes['parsed.error_severity'] in ('ERROR', 'FATAL', 'PANIC')
and log_attributes['parsed.user_name'] = 'authenticator' -- the authenticator role represents the database API
order by timestamp desc
limit 100;
```
Expand All @@ -175,102 +173,88 @@ limit 100;

```sql
select
cast(postgres_logs.timestamp as datetime) as timestamp,
timestamp,
event_message,
parsed.error_severity,
parsed.user_name,
parsed.query,
parsed.detail,
parsed.hint,
parsed.sql_state_code,
parsed.backend_type
from
postgres_logs
cross join unnest(metadata) as metadata
cross join unnest(metadata.parsed) as parsed
where parsed.sql_state_code like '42501' and parsed.user_name = 'authenticator' -- the authenticator role represents the database API
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.hint'] as hint,
log_attributes['parsed.sql_state_code'] as sql_state_code,
log_attributes['parsed.backend_type'] as backend_type
from logs
where
source = 'postgres_logs'
and log_attributes['parsed.sql_state_code'] = '42501'
and log_attributes['parsed.user_name'] = 'authenticator' -- the authenticator role represents the database API
order by timestamp desc
limit 100;
```

<Admonition type="note">

PostgREST error codes are only captured in the logs for projects running V14+. You can check your PostgREST version and upgrade your project in the [General Settings](/dashboard/project/_/settings/general)
The codes in the table above are returned in the response body, not recorded in the logs. Use the queries below to find the failing requests, then read the `code` from the response your client received.

</Admonition>

### Find specific API error
### Find API errors at the gateway

`sb_error_code` is the error code the API gateway recorded for a request, such as `UNAUTHORIZED_MISSING_API_KEY`. It is empty when the request reached PostgREST and failed there.

```sql
select
cast(timestamp as datetime) as timestamp,
status_code,
event_message,
coalesce(proxy_status, 'not_recorded') as error_codes,
path
from
edge_logs
cross join unnest(metadata) as metadata
cross join unnest(response) as response
cross join unnest(request) as request
timestamp,
log_attributes['response.status_code'] as status_code,
log_attributes['response.headers.sb_error_code'] as gateway_error_code,
log_attributes['request.path'] as path,
event_message
from logs
where
status_code >= 300
and regexp_contains(path, '^/rest/v1/')
and regexp_contains(proxy_status, '(?i)THE_RELEVANT_STATUS_CODE');
source = 'edge_logs'
and toInt32OrZero(log_attributes['response.status_code']) >= 300
and match(log_attributes['request.path'], '^/rest/v1/')
order by timestamp desc
limit 100;
```

### Count errors per path by hour:

```sql
select
format_timestamp(
"%c",
timestamp_trunc(cast(edge_logs.timestamp as timestamp), hour),
"UTC"
) as hour,
count(proxy_status) as error_count,
path,
coalesce(proxy_status, 'not_recorded') as error_codes
from
edge_logs
cross join unnest(metadata) as metadata
cross join unnest(response) as response
cross join unnest(response.headers) as headers
cross join unnest(request) as request
where status_code >= 300 and regexp_contains(path, '^/rest/v1/')
group by hour, proxy_status, path;
toStartOfHour(timestamp) as hour,
count() as error_count,
log_attributes['request.path'] as path
from logs
where
source = 'edge_logs'
and toInt32OrZero(log_attributes['response.status_code']) >= 300
and match(log_attributes['request.path'], '^/rest/v1/')
group by hour, path
order by hour desc
limit 100;
```

### Find data API request from specific authenticated user

```sql
select
cast(timestamp as datetime) as timestamp,
timestamp,
event_message,
cf_connecting_ip as requesters_ip,
url as request_url,
request.method as request_method,
sb.auth_user as user_id,
apikey_payload.role as apikey_role,
authorization_payload.role as authorization_token_role,
user_agent,
city,
country,
continent,
postalCode
from
edge_logs
cross join unnest(metadata) as metadata
cross join unnest(request) as request
cross join unnest(sb) as sb
cross join unnest(jwt) as jwt
cross join unnest(jwt.apikey) as jwt_apikey
cross join unnest(jwt_apikey.payload) as apikey_payload
cross join unnest(authorization) as authorization_key
cross join unnest(authorization_key.payload) as authorization_payload
cross join unnest(headers) as headers
cross join unnest(cf) as cf
cross join unnest(response) as response
where regexp_contains(path, '^/rest/v1/') and sb.auth_user = 'SOME_USER_ID' -- <---ADD USER_ID from auth.users table
order by timestamp desc;
log_attributes['request.headers.cf_connecting_ip'] as requesters_ip,
log_attributes['request.url'] as request_url,
log_attributes['request.method'] as request_method,
log_attributes['request.sb.jwt.authorization.payload.subject'] as user_id,
log_attributes['request.sb.jwt.apikey.payload.role'] as apikey_role,
log_attributes['request.sb.jwt.authorization.payload.role'] as authorization_token_role,
log_attributes['request.headers.user_agent'] as user_agent,
log_attributes['request.cf.city'] as city,
log_attributes['request.cf.country'] as country,
log_attributes['request.cf.postalCode'] as postalCode
from logs
where
source = 'edge_logs'
and match(log_attributes['request.path'], '^/rest/v1/')
and log_attributes['request.sb.jwt.authorization.payload.subject'] = 'SOME_USER_ID' -- <---ADD USER_ID from auth.users table
order by timestamp desc
limit 100;
```
11 changes: 4 additions & 7 deletions apps/docs/content/guides/database/extensions/pgaudit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,14 @@ Generates the following log in the [Dashboard's Postgres Logs](/dashboard/projec

## Finding and filtering audit logs

Logs generated by PGAudit can be found in [Postgres Logs](/dashboard/project/_/logs/postgres-logs?s=AUDIT). To find a specific log, you can use the log explorer. Below is a basic example to extract logs referencing `CREATE TABLE` events
Logs generated by PGAudit can be found in [Postgres Logs](/dashboard/project/_/logs/postgres-logs?s=AUDIT). To find a specific log, you can use the [SQL Editor](/dashboard/project/_/sql/new?skip=true&source=logs) with the query source set to **Logs**. Below is a basic example to extract logs referencing `CREATE TABLE` events

```sql
select
cast(t.timestamp as datetime) as timestamp,
timestamp,
event_message
from
postgres_logs as t
cross join unnest(metadata) as m
cross join unnest(m.parsed) as p
where event_message like 'AUDIT%CREATE TABLE%'
from logs
where source = 'postgres_logs' and event_message like 'AUDIT%CREATE TABLE%'
order by timestamp desc
limit 100;
```
Expand Down
34 changes: 16 additions & 18 deletions apps/docs/content/guides/database/postgres/timeouts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,36 +126,34 @@ language sql;

The Supabase Dashboard contains tools to help you identify timed-out and long-running queries.

### Using the Logs Explorer
### Using the SQL Editor

Go to the [Logs Explorer](/dashboard/project/_/logs/explorer), and run the following query to identify timed-out events (`statement timeout`) and queries that successfully run for longer than 10 seconds (`duration`).
Go to the [SQL Editor](/dashboard/project/_/sql/new?skip=true&source=logs), set the query source to **Logs**, and run the following query to identify timed-out events (`statement timeout`) and queries that successfully run for longer than 10 seconds (`duration`).

```sql
select
cast(postgres_logs.timestamp as datetime) as timestamp,
timestamp,
event_message,
parsed.error_severity,
parsed.user_name,
parsed.query,
parsed.detail,
parsed.hint,
parsed.sql_state_code,
parsed.backend_type
from
postgres_logs
cross join unnest(metadata) as metadata
cross join unnest(metadata.parsed) as parsed
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.hint'] as hint,
log_attributes['parsed.sql_state_code'] as sql_state_code,
log_attributes['parsed.backend_type'] as backend_type
from logs
where
regexp_contains(event_message, 'duration|statement timeout')
source = 'postgres_logs'
and match(event_message, 'duration|statement timeout')
-- (OPTIONAL) MODIFY OR REMOVE
and parsed.user_name = 'authenticator' -- <--------CHANGE
and log_attributes['parsed.user_name'] = 'authenticator' -- <--------CHANGE
order by timestamp desc
limit 100;
```

### Using the Query Performance page

Go to the [Query Performance page](/dashboard/project/_/advisors/query-performance?preset=slowest_execution) and filter by relevant role and query speeds. This only identifies slow-running but successful queries. Unlike the Log Explorer, it does not show you timed-out queries.
Go to the [Query Performance page](/dashboard/project/_/advisors/query-performance?preset=slowest_execution) and filter by relevant role and query speeds. This only identifies slow-running but successful queries. Unlike the logs, it does not show you timed-out queries.

### Understanding roles in logs

Expand All @@ -178,5 +176,5 @@ Filter by the `parsed.user_name` field to only retrieve logs made by specific us
... query
where
-- find events from the relevant role
parsed.user_name = '<ROLE>'
log_attributes['parsed.user_name'] = '<ROLE>'
```
6 changes: 3 additions & 3 deletions apps/docs/content/guides/database/prisma.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t
</StepHikeCompact.Details>

<StepHikeCompact.Code>
<Tabs>
<Tabs type="underlined" size="small">
<TabPanel id="serverful" label="server-based deployments">
In your .env file, set the DATABASE_URL variable to your connection string
```text .env
Expand Down Expand Up @@ -192,7 +192,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t
</StepHikeCompact.Details>

<StepHikeCompact.Code>
<Tabs>
<Tabs type="underlined" size="small">
<TabPanel id="serverful" label="server-based deployments">
```ts prisma.config.ts
import "dotenv/config";
Expand Down Expand Up @@ -240,7 +240,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t

<StepHikeCompact.Code>

<Tabs>
<Tabs type="underlined" size="small">

<TabPanel id="new-projects" label="New Projects">
Create new tables in your prisma.schema file
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/guides/database/tables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ Views can restrict the amount and type of data presented to a user. Instead of a

### Materialized views

A [materialized view](https://www.postgresql.org/docs/12/rules-materializedviews.html) is a form of view but it also stores the results to disk. In subsequent reads of a materialized view, the time taken to return its results would be much faster than a conventional view. This is because the data is readily available for a materialized view while the conventional view executes the underlying query each time it is called.
A [materialized view](https://www.postgresql.org/docs/current/rules-materializedviews.html) is a form of view but it also stores the results to disk. In subsequent reads of a materialized view, the time taken to return its results would be much faster than a conventional view. This is because the data is readily available for a materialized view while the conventional view executes the underlying query each time it is called.

Using our example above, a materialized view can be created like this:

Expand Down Expand Up @@ -678,7 +678,7 @@ Creating a materialized view is not a solution to inefficient queries. You shoul
## Resources

- [Official Docs: Create table](https://www.postgresql.org/docs/current/sql-createtable.html)
- [Official Docs: Create view](https://www.postgresql.org/docs/12/sql-createview.html)
- [Official Docs: Create view](https://www.postgresql.org/docs/current/sql-createview.html)
- [Postgres Tutorial: Create tables](https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-create-table/)
- [Postgres Tutorial: Add column](https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-add-column/)
- [Postgres Tutorial: Views](https://www.postgresqltutorial.com/postgresql-views/)
Loading
Loading