mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(functions): migrate EdgeFunctionRecentErrors to logs.all.otel (#47489)
## Problem
The edge function overview page (gated by the \`edgeFunctionsOverview\`
flag) runs three log queries against the legacy BigQuery \`logs.all\`
endpoint. These need to move to the ClickHouse-backed \`logs.all.otel\`
endpoint to stay consistent with the rest of the logs migration.
## Fix
Rewrote the three SQL query builders in
\`EdgeFunctionRecentErrors.utils.ts\` from BigQuery syntax to ClickHouse
syntax targeting the \`edge_logs\` OTEL schema. Added \`{ useOtel: true
}\` to all three \`useLogsQuery\` calls to route them to the
\`logs.all.otel\` endpoint.
Key field mappings used:
- \`metadata[0].function_id\` -> \`LogAttributes['function_id']\`
- \`metadata[0].execution_id\` -> \`LogAttributes['execution_id']\`
- \`metadata[0].level\` / \`metadata[0].event_type\` -> \`SeverityText\`
/ \`LogAttributes['event_type']\`
- \`timestamp\` -> \`toUnixTimestamp64Micro(Timestamp)\` (preserves
microsecond integer format expected downstream)
- HTTP invocations filtered by \`LogAttributes['event_type'] =
'Request'\`
- Runtime logs filtered by \`LogAttributes['event_type'] = 'Log'\`
## How to test
- Enable the \`edgeFunctionsOverview\` feature flag on a project that
has an edge function with recent invocations and errors
- Navigate to the function overview page
- The "Errors since last deploy" section should load and display error
groups correctly
- Each error group should show count, last seen time, method, status
code, and execution time
- Expanding a group should show related runtime logs beneath it
- With no errors, the empty state should show the invocation count since
last deploy
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved Edge Function recent errors with more accurate filtering of
server-side failures.
* Expanded Edge Function runtime log coverage for clearer event
visibility.
* Refreshed Edge Function since-deploy invocation counts to better match
current log querying behavior.
* **Documentation**
* Refined “minimal, well-formed query” guidance, including requiring an
identifying comment at the start and clearer log source scoping
examples.
* **Tests**
* Updated unit tests to match the revised SQL/log filtering and
selection logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
91d70a8c38
commit
0bfca221e9
4 files changed
+90
-32
No files matched your search
@@ -49,9 +49,11 @@ in `log_attributes`.
|
||||
precision, no trailing `Z`). In the Logs Explorer the selected time range is
|
||||
applied for you, so you rarely need to write a `timestamp` filter by hand.
|
||||
|
||||
A minimal, well-formed query:
|
||||
A minimal, well-formed query. Lead with a comment naming the query, filter by
|
||||
`source`, and always `limit`:
|
||||
|
||||
```sql
|
||||
-- recent edge requests
|
||||
select timestamp, event_message
|
||||
from logs
|
||||
where source = 'edge_logs'
|
||||
@@ -155,8 +157,9 @@ rejects `count(*)` and `select *` — use `count()` and list the columns you nee
|
||||
These keep queries correct and cheap. Log tables are large; an unbounded scan
|
||||
reads far more data than you need.
|
||||
|
||||
- **Start every query with an identifying comment** (e.g. `-- errors since last deploy`). It labels the query in logs and review, and makes each of several queries in a file easy to tell apart.
|
||||
- **Always include a `LIMIT`.** Even for aggregates while you iterate.
|
||||
- **Always filter by `source`.** It scopes the query to one service.
|
||||
- **Always query `from logs where source = '...'`.** There is no per-service table (no `edge_logs`, `postgres_logs`, etc. table) — there is one `logs` table, and `source` scopes it to a service. Filtering by `source` is required, not just an optimization.
|
||||
- **Keep the time range tight.** A smaller window returns results faster.
|
||||
- **Filter on the real columns** (`source`, `timestamp`) before reaching into
|
||||
`log_attributes`.
|
||||
|
||||
+6
-3
@@ -94,7 +94,8 @@ export const EdgeFunctionRecentErrors = ({
|
||||
iso_timestamp_start: isoTimestampStart,
|
||||
iso_timestamp_end: isoTimestampEnd,
|
||||
},
|
||||
isQueryEnabled
|
||||
isQueryEnabled,
|
||||
{ useOtel: true }
|
||||
)
|
||||
|
||||
const recentErrorGroupsBase = useMemo(
|
||||
@@ -112,7 +113,8 @@ export const EdgeFunctionRecentErrors = ({
|
||||
iso_timestamp_start: isoTimestampStart,
|
||||
iso_timestamp_end: isoTimestampEnd,
|
||||
},
|
||||
Boolean(projectRef && sinceLastDeployInvocationCountSql && isoTimestampStart)
|
||||
Boolean(projectRef && sinceLastDeployInvocationCountSql && isoTimestampStart),
|
||||
{ useOtel: true }
|
||||
)
|
||||
|
||||
const relatedExecutionIds = useMemo(
|
||||
@@ -136,7 +138,8 @@ export const EdgeFunctionRecentErrors = ({
|
||||
iso_timestamp_start: isoTimestampStart,
|
||||
iso_timestamp_end: isoTimestampEnd,
|
||||
},
|
||||
Boolean(projectRef && functionRuntimeLogsSql && isoTimestampStart)
|
||||
Boolean(projectRef && functionRuntimeLogsSql && isoTimestampStart),
|
||||
{ useOtel: true }
|
||||
)
|
||||
const queryError =
|
||||
toAlertError(recentErrorInvocationsError) ?? toAlertError(functionRuntimeLogsError)
|
||||
|
||||
+36
-6
@@ -10,6 +10,7 @@ import {
|
||||
getNoErrorsSinceLastDeployMessage,
|
||||
getRecentErrorGroups,
|
||||
getRecentErrorGroupsBase,
|
||||
getRecentErrorInvocationsSql,
|
||||
getRelatedExecutionIds,
|
||||
getSinceLastDeployInvocationCount,
|
||||
getSinceLastDeployInvocationCountSql,
|
||||
@@ -47,10 +48,37 @@ describe('EdgeFunctionRecentErrors.utils', () => {
|
||||
executionIds: ['exec_1', "exec_'2"],
|
||||
limit: 25,
|
||||
})
|
||||
)
|
||||
.toBe(`select id, function_logs.timestamp, event_message, metadata.event_type, metadata.function_id, metadata.execution_id, metadata.level from function_logs
|
||||
cross join unnest(metadata) as metadata
|
||||
where metadata.function_id = 'fn_''123' and metadata.execution_id in ('exec_1', 'exec_''2')
|
||||
).toBe(`-- runtime logs for error groups
|
||||
select
|
||||
toUnixTimestamp64Micro(timestamp) as timestamp,
|
||||
event_message,
|
||||
log_attributes['level'] as level,
|
||||
log_attributes['event_type'] as event_type,
|
||||
log_attributes['function_id'] as function_id,
|
||||
log_attributes['execution_id'] as execution_id
|
||||
from logs
|
||||
where
|
||||
source = 'function_logs'
|
||||
and log_attributes['function_id'] = 'fn_''123'
|
||||
and log_attributes['execution_id'] in ('exec_1', 'exec_''2')
|
||||
order by timestamp desc
|
||||
limit 25`)
|
||||
})
|
||||
|
||||
it('builds recent error invocations SQL and escapes the function id', () => {
|
||||
expect(getRecentErrorInvocationsSql("fn_'123", 25)).toBe(`-- errors since last deploy
|
||||
select
|
||||
toUnixTimestamp64Micro(timestamp) as timestamp,
|
||||
event_message,
|
||||
log_attributes['request.method'] as method,
|
||||
log_attributes['response.status_code'] as status_code,
|
||||
toFloat64OrZero(log_attributes['execution_time_ms']) as execution_time_ms,
|
||||
log_attributes['execution_id'] as execution_id
|
||||
from logs
|
||||
where
|
||||
source = 'function_edge_logs'
|
||||
and log_attributes['function_id'] = 'fn_''123'
|
||||
and toInt32OrZero(log_attributes['response.status_code']) >= 500
|
||||
order by timestamp desc
|
||||
limit 25`)
|
||||
})
|
||||
@@ -83,9 +111,11 @@ limit 25`)
|
||||
|
||||
it('builds the since-deploy invocation count query and empty-state message', () => {
|
||||
expect(getSinceLastDeployInvocationCountSql()).toContain(
|
||||
'SELECT count(*) as count FROM function_edge_logs'
|
||||
"select count() as count from logs where source = 'function_edge_logs'"
|
||||
)
|
||||
expect(getSinceLastDeployInvocationCountSql()).toContain(
|
||||
"log_attributes['function_id'] = '__pending__'"
|
||||
)
|
||||
expect(getSinceLastDeployInvocationCountSql()).toContain("(`function_id` = '__pending__')")
|
||||
|
||||
expect(
|
||||
getSinceLastDeployInvocationCount([
|
||||
|
||||
+43
-21
@@ -2,11 +2,8 @@ import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
|
||||
import { parseEdgeFunctionEventMessage } from '../EdgeFunctionRecentInvocations.utils'
|
||||
import { LOGS_TABLES } from '@/components/interfaces/Settings/Logs/Logs.constants'
|
||||
import type { LogData } from '@/components/interfaces/Settings/Logs/Logs.types'
|
||||
import {
|
||||
genCountQuery,
|
||||
genDefaultQuery,
|
||||
isUnixMicro,
|
||||
unixMicroToIsoTimestamp,
|
||||
} from '@/components/interfaces/Settings/Logs/Logs.utils'
|
||||
@@ -196,20 +193,32 @@ export const getStatusBadgeVariant = (statusCode?: string) => {
|
||||
export const getRecentErrorInvocationsSql = (
|
||||
functionId?: string,
|
||||
limit = RECENT_ERROR_INVOCATIONS_LIMIT
|
||||
) =>
|
||||
genDefaultQuery(
|
||||
LOGS_TABLES.fn_edge,
|
||||
{
|
||||
function_id: functionId ?? '__pending__',
|
||||
'status_code.error': true,
|
||||
},
|
||||
limit
|
||||
)
|
||||
): string => {
|
||||
const id = escapeSqlString(functionId ?? '__pending__')
|
||||
return `
|
||||
-- errors since last deploy
|
||||
select
|
||||
toUnixTimestamp64Micro(timestamp) as timestamp,
|
||||
event_message,
|
||||
log_attributes['request.method'] as method,
|
||||
log_attributes['response.status_code'] as status_code,
|
||||
toFloat64OrZero(log_attributes['execution_time_ms']) as execution_time_ms,
|
||||
log_attributes['execution_id'] as execution_id
|
||||
from logs
|
||||
where
|
||||
source = 'function_edge_logs'
|
||||
and log_attributes['function_id'] = '${id}'
|
||||
and toInt32OrZero(log_attributes['response.status_code']) >= 500
|
||||
order by timestamp desc
|
||||
limit ${limit}
|
||||
`.trim()
|
||||
}
|
||||
|
||||
export const getSinceLastDeployInvocationCountSql = (functionId?: string) =>
|
||||
genCountQuery(LOGS_TABLES.fn_edge, {
|
||||
function_id: functionId ?? '__pending__',
|
||||
})
|
||||
export const getSinceLastDeployInvocationCountSql = (functionId?: string): string => {
|
||||
const id = escapeSqlString(functionId ?? '__pending__')
|
||||
return `-- invocation count since last deploy
|
||||
select count() as count from logs where source = 'function_edge_logs' and log_attributes['function_id'] = '${id}'`
|
||||
}
|
||||
|
||||
export const getSinceLastDeployInvocationCount = (invocationCountRows: LogData[]) => {
|
||||
const count = Number(invocationCountRows[0]?.count ?? 0)
|
||||
@@ -238,16 +247,29 @@ export const getFunctionRuntimeLogsSql = ({
|
||||
functionId?: string
|
||||
executionIds: string[]
|
||||
limit?: number
|
||||
}) => {
|
||||
}): string => {
|
||||
if (!functionId || executionIds.length === 0) return ''
|
||||
|
||||
const escapedFunctionId = escapeSqlString(functionId)
|
||||
const escapedExecutionIds = executionIds.map((id) => `'${escapeSqlString(id)}'`).join(', ')
|
||||
|
||||
return `select id, function_logs.timestamp, event_message, metadata.event_type, metadata.function_id, metadata.execution_id, metadata.level from function_logs
|
||||
cross join unnest(metadata) as metadata
|
||||
where metadata.function_id = '${escapeSqlString(functionId)}' and metadata.execution_id in (${escapedExecutionIds})
|
||||
return `
|
||||
-- runtime logs for error groups
|
||||
select
|
||||
toUnixTimestamp64Micro(timestamp) as timestamp,
|
||||
event_message,
|
||||
log_attributes['level'] as level,
|
||||
log_attributes['event_type'] as event_type,
|
||||
log_attributes['function_id'] as function_id,
|
||||
log_attributes['execution_id'] as execution_id
|
||||
from logs
|
||||
where
|
||||
source = 'function_logs'
|
||||
and log_attributes['function_id'] = '${escapedFunctionId}'
|
||||
and log_attributes['execution_id'] in (${escapedExecutionIds})
|
||||
order by timestamp desc
|
||||
limit ${limit}`
|
||||
limit ${limit}
|
||||
`.trim()
|
||||
}
|
||||
|
||||
export const getRecentErrorGroupsBase = (
|
||||
|
||||
Reference in new issue
Block a user