mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
aed332b663
## Problem
The Edge Functions report (`observability/edge-functions`) only queries
BigQuery. As part of the broader Reports→ClickHouse OTEL migration
(DEBUG-73), we need each report migrated one at a time behind the
`otelReports` flag.
## Fix
Adds a ClickHouse OTEL SQL variant (`METRIC_SQL_OTEL`) for the 4 Edge
Functions metrics (TotalInvocations, ExecutionStatusCodes,
InvocationsByRegion, ExecutionTime), querying the unified `logs` table
filtered to `source = 'function_edge_logs'`, with fields read from
`log_attributes` (`function_id`, `response.status_code`,
`response.headers.x_sb_edge_region`, `execution_time_ms`) — the same
mapping already used by `edge-functions-last-hour-stats-query.ts`.
`edgeFunctionReports()` now takes a `useOtel` flag that picks between
the BQ and OTEL query sets and forwards it to `fetchLogs`. The page
wires this up via `useFlag('otelReports')`, matching the pattern used
for the Auth report. No behavior change while the flag is off — report
still fetches from BigQuery.
Also removed two pieces of dead code spotted in `report.utils.ts` while
touching it: the unused `useEdgeFnIdToName` hook and a
`STATUS_CODE_COLORS` map that was an exact duplicate of
`REPORT_STATUS_CODE_COLORS` (the one actually imported elsewhere).
This PR is standalone — no dependency on the in-flight Auth report OTEL
stack.
## How to test
- `pnpm vitest run data/reports/v2/edge-functions.config.otel.test.ts` —
9 new tests covering the OTEL SQL shape (single logs table,
unix-microsecond timestamp bucketing, field mapping, filters).
- `pnpm vitest run data/reports` and `pnpm vitest run
data/edge-functions components/interfaces/Reports` — existing suites (46
+ 54 tests) still pass, confirming no regression to the BQ path.
- Manually: with `otelReports` flag enabled, visit a project's Edge
Functions observability report and confirm charts render from the
ClickHouse endpoint.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added OpenTelemetry support for Edge Functions observability metrics,
including invocations, status codes, regional activity, and execution
time.
* Reports can now dynamically use either the standard or OpenTelemetry
logs source.
* **Bug Fixes**
* Improved filtering and timestamp handling for OpenTelemetry-based Edge
Functions metrics.
* Added coverage for status, execution time, function, and region
filters.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
76 lines
3.0 KiB
TypeScript
76 lines
3.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { METRIC_SQL_OTEL } from './edge-functions.config'
|
|
|
|
const sql = (fragment: { toString(): string }) => String(fragment)
|
|
|
|
describe('METRIC_SQL_OTEL', () => {
|
|
it('queries the single OTEL logs table by source, never a per-service table', () => {
|
|
const out = sql(METRIC_SQL_OTEL.TotalInvocations('1h'))
|
|
|
|
expect(out).toContain('from logs')
|
|
expect(out).toContain("source = 'function_edge_logs'")
|
|
expect(out).not.toContain('from function_edge_logs')
|
|
expect(out).not.toContain('cross join unnest')
|
|
})
|
|
|
|
it('emits 16-digit unix-microsecond timestamps bucketed by granularity', () => {
|
|
expect(sql(METRIC_SQL_OTEL.TotalInvocations('1h'))).toContain(
|
|
'toUnixTimestamp(toStartOfHour(timestamp)) * 1000000 as timestamp'
|
|
)
|
|
expect(sql(METRIC_SQL_OTEL.TotalInvocations('1d'))).toContain(
|
|
'toUnixTimestamp(toStartOfDay(timestamp)) * 1000000 as timestamp'
|
|
)
|
|
expect(sql(METRIC_SQL_OTEL.TotalInvocations('5m'))).toContain(
|
|
'toUnixTimestamp(toStartOfMinute(timestamp)) * 1000000 as timestamp'
|
|
)
|
|
})
|
|
|
|
it('reads function_id from log_attributes for total invocations', () => {
|
|
const out = sql(METRIC_SQL_OTEL.TotalInvocations('1h'))
|
|
expect(out).toContain("log_attributes['function_id'] as function_id")
|
|
expect(out).toContain('count() as count')
|
|
})
|
|
|
|
it('reads response status code from log_attributes, coerced to a number', () => {
|
|
const out = sql(METRIC_SQL_OTEL.ExecutionStatusCodes('1h'))
|
|
expect(out).toContain("toInt32OrZero(log_attributes['response.status_code']) as status_code")
|
|
})
|
|
|
|
it('reads region from the response headers attribute and drops empty values', () => {
|
|
const out = sql(METRIC_SQL_OTEL.InvocationsByRegion('1h'))
|
|
expect(out).toContain("log_attributes['response.headers.x_sb_edge_region'] as region")
|
|
expect(out).toContain("log_attributes['response.headers.x_sb_edge_region'] != ''")
|
|
})
|
|
|
|
it('averages execution_time_ms from log_attributes, coerced to a float', () => {
|
|
const out = sql(METRIC_SQL_OTEL.ExecutionTime('1h'))
|
|
expect(out).toContain(
|
|
"avg(toFloat64OrZero(log_attributes['execution_time_ms'])) as avg_execution_time"
|
|
)
|
|
})
|
|
|
|
it('filters by function_id list when functions filter is set', () => {
|
|
const out = sql(METRIC_SQL_OTEL.TotalInvocations('1h', { functions: ['fn-1', 'fn-2'] } as any))
|
|
expect(out).toContain("log_attributes['function_id'] IN ('fn-1','fn-2')")
|
|
})
|
|
|
|
it('applies the numeric status_code filter using toInt32OrZero', () => {
|
|
const out = sql(
|
|
METRIC_SQL_OTEL.ExecutionStatusCodes('1h', {
|
|
status_code: { operator: '>=', value: 500 },
|
|
} as any)
|
|
)
|
|
expect(out).toContain("AND toInt32OrZero(log_attributes['response.status_code']) >= 500")
|
|
})
|
|
|
|
it('applies the numeric execution_time filter using toFloat64OrZero', () => {
|
|
const out = sql(
|
|
METRIC_SQL_OTEL.ExecutionTime('1h', {
|
|
execution_time: { operator: '>=', value: 100 },
|
|
} as any)
|
|
)
|
|
expect(out).toContain("AND toFloat64OrZero(log_attributes['execution_time_ms']) >= 100")
|
|
})
|
|
})
|