mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
assistant log query
This commit is contained in:
1 parent
e1eaa9844c
commit
28c5a82859
5 files changed
+363
-15
No files matched your search
@@ -5,6 +5,7 @@ export interface AssistantSnippetProps {
|
||||
title?: string
|
||||
isChart?: string
|
||||
runQuery?: string
|
||||
logs?: string
|
||||
xAxis?: string
|
||||
yAxis?: string
|
||||
name?: string
|
||||
|
||||
@@ -62,6 +62,7 @@ const MemoizedQueryBlock = memo(
|
||||
isLoading,
|
||||
isDraggable,
|
||||
runQuery,
|
||||
logs,
|
||||
onRunQuery,
|
||||
onDragStart,
|
||||
onUpdateChartConfig,
|
||||
@@ -74,6 +75,7 @@ const MemoizedQueryBlock = memo(
|
||||
isLoading: boolean
|
||||
isDraggable: boolean
|
||||
runQuery: boolean
|
||||
logs?: boolean
|
||||
onRunQuery: (queryType: 'select' | 'mutation') => void
|
||||
onDragStart: (e: DragEvent<Element>) => void
|
||||
onUpdateChartConfig?: ({
|
||||
@@ -97,6 +99,7 @@ const MemoizedQueryBlock = memo(
|
||||
lockColumns
|
||||
label={title}
|
||||
sql={sql}
|
||||
logs={logs}
|
||||
chartConfig={{
|
||||
type: 'bar',
|
||||
cumulative: false,
|
||||
@@ -162,6 +165,9 @@ export const MarkdownPre = ({ children }: { children: any }) => {
|
||||
const title = snippetProps.title || (language === 'edge' ? 'Edge Function' : 'SQL Query')
|
||||
const isChart = snippetProps.isChart === 'true'
|
||||
const runQuery = snippetProps.runQuery === 'true'
|
||||
const isLogs = snippetProps.logs === 'true'
|
||||
|
||||
console.log('raw:', rawContent, snippetProps, isLogs)
|
||||
|
||||
// Strip props from the content for both SQL and edge functions
|
||||
const cleanContent = rawContent.replace(/(?:--|\/\/)\s*props:\s*\{[^}]+\}/, '').trim()
|
||||
@@ -213,6 +219,7 @@ export const MarkdownPre = ({ children }: { children: any }) => {
|
||||
isLoading={isLoading}
|
||||
isDraggable={isDraggableToReports}
|
||||
runQuery={runQuery}
|
||||
logs={isLogs}
|
||||
onRunQuery={onRunQuery}
|
||||
onUpdateChartConfig={({ chartConfig: config }) => {
|
||||
chartConfig.current = { ...chartConfig.current, ...config }
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartC
|
||||
import Results from 'components/interfaces/SQLEditor/UtilityPanel/Results'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
|
||||
import useLogsQuery from 'hooks/analytics/useLogsQuery'
|
||||
import { Parameter, parseParameters } from 'lib/sql-parameters'
|
||||
import { Dashboards } from 'types'
|
||||
import {
|
||||
@@ -28,6 +29,7 @@ import { BlockViewConfiguration } from './BlockViewConfiguration'
|
||||
import { EditQueryButton } from './EditQueryButton'
|
||||
import { ParametersPopover } from './ParametersPopover'
|
||||
import { getCumulativeResults } from './QueryBlock.utils'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export const DEFAULT_CHART_CONFIG: ChartConfig = {
|
||||
type: 'bar',
|
||||
@@ -60,6 +62,8 @@ interface QueryBlockProps {
|
||||
isLoading?: boolean
|
||||
/** Override to prevent running the SQL query provided */
|
||||
runQuery?: boolean
|
||||
/** Whether this is a logs query */
|
||||
logs?: boolean
|
||||
/** Prevent updating of columns for X and Y axes in the chart view */
|
||||
lockColumns?: boolean
|
||||
/** Max height set to render results / charts (Defaults to 250) */
|
||||
@@ -109,6 +113,7 @@ export const QueryBlock = ({
|
||||
isChart = false,
|
||||
isLoading = false,
|
||||
runQuery = false,
|
||||
logs = false,
|
||||
lockColumns = false,
|
||||
draggable = false,
|
||||
isRefreshing = false,
|
||||
@@ -143,22 +148,40 @@ export const QueryBlock = ({
|
||||
onSuccess: (data) => setQueryResult(data.result),
|
||||
})
|
||||
|
||||
const {
|
||||
logData,
|
||||
error: logsError,
|
||||
isLoading: isLoadingLogs,
|
||||
runQuery: runLogsQuery,
|
||||
} = useLogsQuery(
|
||||
ref || '',
|
||||
{
|
||||
sql: sql || '',
|
||||
iso_timestamp_start: dayjs().subtract(7, 'day').toISOString(),
|
||||
iso_timestamp_end: dayjs().toISOString(),
|
||||
},
|
||||
logs && runQuery && Boolean(ref)
|
||||
)
|
||||
|
||||
const handleExecute = () => {
|
||||
if (!sql || isLoading) return
|
||||
|
||||
if (!isReadOnlySelectSQL) {
|
||||
// Skip SQL validation for log queries since they use BigQuery
|
||||
if (!logs && !isReadOnlySelectSQL) {
|
||||
const hasUnknownFunctions = containsUnknownFunction(sql)
|
||||
return setShowWarning(hasUnknownFunctions ? 'hasUnknownFunctions' : 'hasWriteOperation')
|
||||
}
|
||||
|
||||
try {
|
||||
// [Joshen] This is for when we introduced the concept of parameters into our reports
|
||||
// const processedSql = processParameterizedSql(sql, combinedParameterValues)
|
||||
execute({
|
||||
projectRef: ref,
|
||||
connectionString: project?.connectionString,
|
||||
sql,
|
||||
})
|
||||
if (logs) {
|
||||
runLogsQuery()
|
||||
} else {
|
||||
execute({
|
||||
projectRef: ref,
|
||||
connectionString: project?.connectionString,
|
||||
sql,
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to execute query: ${error.message}`)
|
||||
}
|
||||
@@ -168,21 +191,28 @@ export const QueryBlock = ({
|
||||
setChartSettings(chartConfig)
|
||||
}, [chartConfig])
|
||||
|
||||
useEffect(() => {
|
||||
if (logs && logData && !isLoading) {
|
||||
setQueryResult(logData)
|
||||
}
|
||||
}, [logData, isLoading])
|
||||
|
||||
// Run once on mount to parse parameters and notify parent
|
||||
useEffect(() => {
|
||||
if (!!sql && onSetParameter) {
|
||||
const params = parseParameters(sql)
|
||||
onSetParameter(params)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sql])
|
||||
}, [sql, onSetParameter])
|
||||
|
||||
useEffect(() => {
|
||||
if (!!sql && !isLoading && runQuery && isReadOnlySelect(sql) && !!project) {
|
||||
const shouldExecute = !!sql && !isLoading && runQuery && !!project
|
||||
const isValidQuery = logs || (!logs && isReadOnlySelect(sql ?? ''))
|
||||
|
||||
if (shouldExecute && isValidQuery) {
|
||||
handleExecute()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sql, isLoading, runQuery, project])
|
||||
}, [sql, isLoading, runQuery, project?.connectionString])
|
||||
|
||||
useEffect(() => {
|
||||
if (isRefreshing) handleExecute()
|
||||
@@ -193,7 +223,7 @@ export const QueryBlock = ({
|
||||
draggable={draggable}
|
||||
showDragHandle={draggable}
|
||||
tooltip={tooltip}
|
||||
loading={isExecuting}
|
||||
loading={isExecuting || isLoadingLogs}
|
||||
onDragStart={(e: DragEvent<Element>) => onDragStart?.(e)}
|
||||
icon={
|
||||
<SQL_ICON
|
||||
@@ -257,7 +287,7 @@ export const QueryBlock = ({
|
||||
size="tiny"
|
||||
className="w-7 h-7"
|
||||
icon={<Play size={14} />}
|
||||
loading={isExecuting || isLoading}
|
||||
loading={isExecuting || isLoadingLogs || isLoading}
|
||||
disabled={isLoading}
|
||||
onClick={() => {
|
||||
handleExecute()
|
||||
|
||||
@@ -135,6 +135,27 @@ async function handlePost(req: NextApiRequest, res: NextApiResponse) {
|
||||
# You convert sql to supabase-js client code
|
||||
Use the convertSqlToSupabaseJs tool to convert select sql to supabase-js client code. Only provide js code snippets if explicitly asked. If conversion isn't supported, build a postgres function instead and suggest using supabase-js to call it via "const { data, error } = await supabase.rpc('echo', { say: '👋'})"
|
||||
|
||||
# You write log queries for the Supabase Logs Explorer
|
||||
Your purpose is to help users write effective queries for the Supabase Logs Explorer.
|
||||
- First, always use the getLogsExplorerKnowledge tool to get knowledge about how to write log queries
|
||||
- When writing log queries, follow these guidelines:
|
||||
- Always use DATETIME() to format timestamps in a human-readable way
|
||||
- Always include appropriate UNNEST joins when accessing nested fields
|
||||
- Always include a LIMIT clause (max 1000 rows)
|
||||
- Always include appropriate timestamp filters to optimize performance
|
||||
- Avoid selecting entire nested objects, select specific fields instead
|
||||
- When outputting queries, always include a props comment in the first line where logs is set to true:
|
||||
-- props: {"title": "Log Query", "logs": "true", "runQuery": "true"}
|
||||
- The Logs Explorer uses BigQuery syntax and supports all BigQuery functions and operators
|
||||
- Available log sources include:
|
||||
- auth_logs: Authentication/authorization activity
|
||||
- edge_logs: Edge network requests and responses
|
||||
- function_edge_logs: Edge function network activity
|
||||
- function_logs: Edge function console logs
|
||||
- postgres_logs: Database statements
|
||||
- realtime_logs: Client connection information
|
||||
- storage_logs: Object upload and retrieval
|
||||
|
||||
# For all your abilities, follow these instructions:
|
||||
- First look at the list of provided schemas and if needed, get more information about a schema. You will almost always need to retrieve information about the public schema before answering a question.
|
||||
- If the question is about users or involves creating a users table, also retrieve the auth schema.
|
||||
|
||||
@@ -568,5 +568,294 @@ export const getTools = ({
|
||||
`
|
||||
},
|
||||
}),
|
||||
getLogsExplorerKnowledge: tool({
|
||||
description: 'Get knowledge about how to write queries for the Supabase Logs Explorer',
|
||||
parameters: z.object({}),
|
||||
execute: async ({}) => {
|
||||
return stripIndent`
|
||||
# Querying Logs in Supabase Logs Explorer
|
||||
|
||||
You're an expert in writing SQL queries for the Supabase Logs Explorer. The Logs Explorer uses BigQuery and supports all available SQL functions and operators.
|
||||
|
||||
## Available Log Sources
|
||||
|
||||
The following log sources are available from the Sources drop-down:
|
||||
|
||||
* \`auth_logs\`: GoTrue server logs containing authentication/authorization activity
|
||||
* \`edge_logs\`: Edge network logs containing request and response metadata from Cloudflare
|
||||
* \`function_edge_logs\`: Edge network logs for edge functions, containing network requests and response metadata
|
||||
* \`function_logs\`: Function internal logs containing any console logging from edge functions
|
||||
* \`postgres_logs\`: Postgres database logs containing statements executed by connected applications
|
||||
* \`realtime_logs\`: Realtime server logs containing client connection information
|
||||
* \`storage_logs\`: Storage server logs containing object upload and retrieval information
|
||||
|
||||
## Working with Timestamps
|
||||
|
||||
Each log entry has a \`timestamp\` field of type \`TIMESTAMP\`. Important considerations:
|
||||
|
||||
1. Raw timestamps are displayed in unix microseconds
|
||||
2. Use \`DATETIME()\` to convert timestamps to human-readable ISO-8601 format:
|
||||
|
||||
\`\`\`sql
|
||||
-- Raw timestamp (unix microseconds)
|
||||
SELECT timestamp FROM edge_logs;
|
||||
-- Returns: 1664270180000
|
||||
|
||||
-- Formatted timestamp
|
||||
SELECT DATETIME(timestamp) FROM edge_logs;
|
||||
-- Returns: 2022-09-27T09:17:10.439Z
|
||||
\`\`\`
|
||||
|
||||
## Working with Nested Arrays
|
||||
|
||||
Log events store metadata as nested arrays of objects. To query nested data:
|
||||
|
||||
1. Use \`UNNEST()\` for each array level you want to access
|
||||
2. Add joins for each level of nesting
|
||||
3. Reference nested objects using aliases
|
||||
|
||||
Example querying edge logs:
|
||||
|
||||
\`\`\`sql
|
||||
SELECT
|
||||
DATETIME(timestamp),
|
||||
request.method,
|
||||
header.cf_ipcountry
|
||||
FROM
|
||||
edge_logs AS t
|
||||
CROSS JOIN UNNEST(t.metadata) AS metadata
|
||||
CROSS JOIN UNNEST(metadata.request) AS request
|
||||
CROSS JOIN UNNEST(request.headers) AS header;
|
||||
\`\`\`
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always include a filter on \`timestamp\` to optimize query performance
|
||||
2. Avoid selecting large nested objects - select individual values instead
|
||||
3. Use \`LIMIT\` to reduce returned rows (max 1000 rows per query)
|
||||
|
||||
Examples:
|
||||
|
||||
### Edge function 500 error
|
||||
\`\`\`sql
|
||||
select id, function_edge_logs.timestamp, event_message, response.status_code, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version from function_edge_logs
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.response) as response
|
||||
cross join unnest(m.request) as request
|
||||
where (response.status_code between 500 and 599)
|
||||
order by timestamp desc
|
||||
limit 100
|
||||
\`\`\`
|
||||
### ❌ Avoid selecting entire metadata objects
|
||||
\`\`\`sql
|
||||
SELECT
|
||||
DATETIME(timestamp),
|
||||
m AS metadata -- Selects entire metadata object
|
||||
FROM
|
||||
edge_logs AS t
|
||||
CROSS JOIN UNNEST(t.metadata) AS m;
|
||||
\`\`\`
|
||||
|
||||
### ✅ Select specific needed values
|
||||
\`\`\`sql
|
||||
SELECT
|
||||
DATETIME(timestamp),
|
||||
r.method -- Select only required fields
|
||||
FROM
|
||||
edge_logs AS t
|
||||
CROSS JOIN UNNEST(t.metadata) AS m
|
||||
CROSS JOIN UNNEST(m.request) AS r;
|
||||
\`\`\`
|
||||
|
||||
## Common Query Examples
|
||||
|
||||
### Get user IP addresses from edge logs:
|
||||
\`\`\`sql
|
||||
SELECT
|
||||
DATETIME(timestamp),
|
||||
h.x_real_ip
|
||||
FROM
|
||||
edge_logs
|
||||
CROSS JOIN UNNEST(metadata) AS m
|
||||
CROSS JOIN UNNEST(m.request) AS r
|
||||
CROSS JOIN UNNEST(r.headers) AS h
|
||||
WHERE
|
||||
h.x_real_ip IS NOT NULL
|
||||
AND r.method = "GET"
|
||||
LIMIT 1000;
|
||||
\`\`\`
|
||||
|
||||
### Filter logs by time range:
|
||||
\`\`\`sql
|
||||
SELECT
|
||||
DATETIME(timestamp) as time,
|
||||
event_message
|
||||
FROM postgres_logs
|
||||
WHERE
|
||||
timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1000;
|
||||
\`\`\`
|
||||
|
||||
Remember:
|
||||
- The Logs Explorer has a maximum of 1000 rows per query
|
||||
- Log retention depends on your project's pricing plan
|
||||
- Use appropriate timestamp filters to optimize performance
|
||||
- Avoid selecting entire nested objects when possible
|
||||
|
||||
## Logs Field Reference
|
||||
|
||||
Below is the complete field reference for each log source. To access nested fields, you'll need to use the appropriate UNNEST joins as shown in the examples above.
|
||||
|
||||
### Function Edge Logs (\`function_edge_logs\`)
|
||||
|
||||
| Field | Type |
|
||||
|-------|------|
|
||||
| event_message | string |
|
||||
| id | string |
|
||||
| timestamp | datetime |
|
||||
| metadata.deployment_id | string |
|
||||
| metadata.execution_time_ms | number |
|
||||
| metadata.function_id | string |
|
||||
| metadata.project_ref | string |
|
||||
| metadata.request.headers.accept | string |
|
||||
| metadata.request.headers.content_length | string |
|
||||
| metadata.request.headers.host | string |
|
||||
| metadata.request.headers.user_agent | string |
|
||||
| metadata.request.host | string |
|
||||
| metadata.request.method | string |
|
||||
| metadata.request.pathname | string |
|
||||
| metadata.request.protocol | string |
|
||||
| metadata.request.url | string |
|
||||
| metadata.response.headers.content_length | string |
|
||||
| metadata.response.headers.content_type | string |
|
||||
| metadata.response.headers.date | string |
|
||||
| metadata.response.headers.server | string |
|
||||
| metadata.response.headers.vary | string |
|
||||
| metadata.response.status_code | number |
|
||||
| metadata.version | string |
|
||||
|
||||
### API Edge Logs (\`edge_logs\`)
|
||||
|
||||
| Field | Type |
|
||||
|-------|------|
|
||||
| id | string |
|
||||
| timestamp | datetime |
|
||||
| event_message | string |
|
||||
| identifier | string |
|
||||
| metadata.load_balancer_redirect_identifier | string |
|
||||
| metadata.request.cf.asOrganization | string |
|
||||
| metadata.request.cf.asn | number |
|
||||
| metadata.request.cf.botManagement.corporateProxy | boolean |
|
||||
| metadata.request.cf.botManagement.detectionIds | number[] |
|
||||
| metadata.request.cf.botManagement.ja3Hash | string |
|
||||
| metadata.request.cf.botManagement.score | number |
|
||||
| metadata.request.cf.botManagement.staticResource | boolean |
|
||||
| metadata.request.cf.botManagement.verifiedBot | boolean |
|
||||
| metadata.request.cf.city | string |
|
||||
| metadata.request.cf.clientTcpRtt | number |
|
||||
| metadata.request.cf.clientTrustScore | number |
|
||||
| metadata.request.cf.colo | string |
|
||||
| metadata.request.cf.continent | string |
|
||||
| metadata.request.cf.country | string |
|
||||
| metadata.request.cf.edgeRequestKeepAliveStatus | number |
|
||||
| metadata.request.cf.httpProtocol | string |
|
||||
| metadata.request.cf.latitude | string |
|
||||
| metadata.request.cf.longitude | string |
|
||||
| metadata.request.cf.metroCode | string |
|
||||
| metadata.request.cf.postalCode | string |
|
||||
| metadata.request.cf.region | string |
|
||||
| metadata.request.cf.timezone | string |
|
||||
| metadata.request.cf.tlsCipher | string |
|
||||
| metadata.request.cf.tlsClientAuth.certPresented | string |
|
||||
| metadata.request.cf.tlsClientAuth.certRevoked | string |
|
||||
| metadata.request.cf.tlsClientAuth.certVerified | string |
|
||||
| metadata.request.cf.tlsExportedAuthenticator.clientFinished | string |
|
||||
| metadata.request.cf.tlsExportedAuthenticator.clientHandshake | string |
|
||||
| metadata.request.cf.tlsExportedAuthenticator.serverFinished | string |
|
||||
| metadata.request.cf.tlsExportedAuthenticator.serverHandshake | string |
|
||||
| metadata.request.cf.tlsVersion | string |
|
||||
| metadata.request.headers.cf_connecting_ip | string |
|
||||
| metadata.request.headers.cf_ipcountry | string |
|
||||
| metadata.request.headers.cf_ray | string |
|
||||
| metadata.request.headers.host | string |
|
||||
| metadata.request.headers.referer | string |
|
||||
| metadata.request.headers.x_client_info | string |
|
||||
| metadata.request.headers.x_forwarded_proto | string |
|
||||
| metadata.request.headers.x_real_ip | string |
|
||||
| metadata.request.host | string |
|
||||
| metadata.request.method | string |
|
||||
| metadata.request.path | string |
|
||||
| metadata.request.protocol | string |
|
||||
| metadata.request.search | string |
|
||||
| metadata.request.url | string |
|
||||
| metadata.response.headers.cf_cache_status | string |
|
||||
| metadata.response.headers.cf_ray | string |
|
||||
| metadata.response.headers.content_location | string |
|
||||
| metadata.response.headers.content_range | string |
|
||||
| metadata.response.headers.content_type | string |
|
||||
| metadata.response.headers.date | string |
|
||||
| metadata.response.headers.sb_gateway_version | string |
|
||||
| metadata.response.headers.transfer_encoding | string |
|
||||
| metadata.response.headers.x_kong_proxy_latency | string |
|
||||
| metadata.response.origin_time | number |
|
||||
| metadata.response.status_code | number |
|
||||
|
||||
### PostgREST Logs (\`postgrest_logs\`)
|
||||
|
||||
| Field | Type |
|
||||
|-------|------|
|
||||
| event_message | string |
|
||||
| id | string |
|
||||
| identifier | string |
|
||||
| timestamp | datetime |
|
||||
| metadata.host | string |
|
||||
|
||||
### Auth Logs (\`auth_logs\`)
|
||||
|
||||
| Field | Type |
|
||||
|-------|------|
|
||||
| event_message | string |
|
||||
| id | string |
|
||||
| timestamp | datetime |
|
||||
| metadata.auth_event.action | string |
|
||||
| metadata.auth_event.actor_id | string |
|
||||
| metadata.auth_event.actor_username | string |
|
||||
| metadata.auth_event.log_type | string |
|
||||
| metadata.auth_event.traits.provider | string |
|
||||
| metadata.auth_event.traits.user_email | string |
|
||||
| metadata.auth_event.traits.user_id | string |
|
||||
| metadata.auth_event.traits.user_phone | string |
|
||||
| metadata.component | string |
|
||||
| metadata.duration | number |
|
||||
| metadata.host | string |
|
||||
| metadata.level | string |
|
||||
| metadata.method | string |
|
||||
| metadata.msg | string |
|
||||
| metadata.path | string |
|
||||
| metadata.referer | string |
|
||||
| metadata.remote_addr | string |
|
||||
| metadata.status | number |
|
||||
| metadata.timestamp | string |
|
||||
|
||||
Example query using nested fields:
|
||||
\`\`\`sql
|
||||
-- Query auth logs for specific user actions
|
||||
SELECT
|
||||
DATETIME(timestamp) as time,
|
||||
metadata.auth_event.action,
|
||||
metadata.auth_event.actor_username,
|
||||
metadata.auth_event.traits.user_email
|
||||
FROM auth_logs
|
||||
CROSS JOIN UNNEST(metadata.auth_event) as auth_event
|
||||
WHERE
|
||||
metadata.auth_event.action = 'login'
|
||||
AND timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 24 HOUR)
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1000;
|
||||
\`\`\`
|
||||
`
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user