assistant log query

This commit is contained in:
Saxon Fletcher committed 2025-02-18 21:36:55 +10:00
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.
+289
View File
@@ -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;
\`\`\`
`
},
}),
}
}