mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 10:25:06 +03:00
## Problem Unified Logs does not expose Workers logs, so users cannot search Workers ingress, runtime, or build events alongside other services. ## Fix Add a Workers log type that classifies all three Workers OTEL streams. Gate the option and any persisted Workers filters with the existing Workers feature flag. ## How to test - Enable the Workers feature flag and open Unified Logs. - Select Workers from the Log Type filter. - Expected result: Unified Logs shows ingress, runtime, and build events with the Workers icon. - Disable the Workers feature flag and load a URL containing `log_type:eq:workers`. - Expected result: the Workers option and filter are removed, and Workers logs are not queried. - Run `./node_modules/.bin/vitest --run components/interfaces/UnifiedLogs/UnifiedLogs.queries.test.ts components/interfaces/UnifiedLogs/UnifiedLogs.utils.test.ts data/workers/worker-logs-query.test.ts` from `apps/studio`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Workers as a selectable log type in Unified Logs. * Unified Logs now combines worker ingress, guest, and API streams under the Workers category. * Added a dedicated Workers icon and worker log filtering. * **Improvements** * Worker filters and URL parameters respect feature availability. * Worker details show relevant metadata while omitting unavailable HTTP fields. * Improved handling of worker log levels, statuses, and raw data. * Added stronger validation for unified log data. * **Tests** * Added coverage for worker routing, filtering, feature visibility, parsing, and metadata redaction. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
import { queryOptions } from '@tanstack/react-query'
|
|
import { z } from 'zod'
|
|
|
|
import { workersKeys } from './keys'
|
|
import type { LogData } from '@/components/interfaces/Settings/Logs/Logs.types'
|
|
import { otelTimestampToMicros } from '@/components/interfaces/Settings/Logs/Logs.utils.otel'
|
|
import { executeAnalyticsSql } from '@/data/logs/execute-analytics-sql'
|
|
import { logsAllEndpointUrl } from '@/data/logs/logs-endpoint'
|
|
import { analyticsLiteral, safeSql } from '@/data/logs/safe-analytics-sql'
|
|
import { IS_PLATFORM } from '@/lib/constants'
|
|
import { WORKER_LOG_SOURCES } from '@/lib/constants/workers'
|
|
|
|
export type WorkerLogStream = keyof typeof WORKER_LOG_SOURCES
|
|
|
|
export const WORKER_LOG_STREAM_LABEL: Record<WorkerLogStream, string> = {
|
|
requests: 'Invocations',
|
|
output: 'Logs',
|
|
builds: 'Activity',
|
|
}
|
|
|
|
// Both are read from `log_attributes` rather than the endpoint's own `source` column:
|
|
// that column is derived from a mapping which does not currently classify worker rows.
|
|
const WORKER_NAME_KEY = 'worker'
|
|
const STREAM_KEY = 'source'
|
|
|
|
const LOG_LIMIT = 100
|
|
|
|
const workerLogRowSchema = z.object({
|
|
id: z.string(),
|
|
timestamp: z.union([z.string(), z.number()]),
|
|
severity: z.string().nullish(),
|
|
message: z.string().nullish(),
|
|
})
|
|
|
|
export type WorkerLogsVariables = {
|
|
projectRef?: string
|
|
name?: string
|
|
stream: WorkerLogStream
|
|
iso_timestamp_start: string
|
|
iso_timestamp_end: string
|
|
message?: string
|
|
}
|
|
|
|
export const workerLogsSql = (
|
|
name: string,
|
|
stream: WorkerLogStream,
|
|
{ message }: Pick<WorkerLogsVariables, 'message'> = {}
|
|
) => {
|
|
const messageFilter = message
|
|
? safeSql` and event_message ilike ${analyticsLiteral(`%${message}%`)}`
|
|
: safeSql``
|
|
|
|
return safeSql`select id, timestamp, severity_text as severity, event_message as message from logs where log_attributes[${analyticsLiteral(WORKER_NAME_KEY)}] = ${analyticsLiteral(name)} and log_attributes[${analyticsLiteral(STREAM_KEY)}] = ${analyticsLiteral(WORKER_LOG_SOURCES[stream])}${messageFilter} order by timestamp desc limit ${analyticsLiteral(LOG_LIMIT)}`
|
|
}
|
|
|
|
export const parseWorkerLogRows = (result: unknown): LogData[] =>
|
|
z
|
|
.array(workerLogRowSchema)
|
|
.parse(result ?? [])
|
|
.map((row) => ({
|
|
id: row.id,
|
|
timestamp: otelTimestampToMicros(row.timestamp),
|
|
event_message: row.message ?? '',
|
|
severity_text: row.severity ?? '',
|
|
}))
|
|
|
|
async function getWorkerLogs(
|
|
{
|
|
projectRef,
|
|
name,
|
|
stream,
|
|
iso_timestamp_start,
|
|
iso_timestamp_end,
|
|
message,
|
|
}: WorkerLogsVariables,
|
|
signal?: AbortSignal
|
|
): Promise<LogData[]> {
|
|
if (!projectRef) throw new Error('projectRef is required')
|
|
if (!name) throw new Error('name is required')
|
|
|
|
const data = await executeAnalyticsSql({
|
|
projectRef,
|
|
endpoint: logsAllEndpointUrl(true),
|
|
sql: workerLogsSql(name, stream, { message }),
|
|
iso_timestamp_start,
|
|
iso_timestamp_end,
|
|
signal,
|
|
})
|
|
|
|
return parseWorkerLogRows(data?.result)
|
|
}
|
|
|
|
export const workerLogsQueryOptions = (variables: WorkerLogsVariables) => {
|
|
const { projectRef, name, stream, iso_timestamp_start, iso_timestamp_end } = variables
|
|
const message = variables.message?.trim() || undefined
|
|
|
|
return queryOptions({
|
|
queryKey: workersKeys.logs(projectRef, name, stream, {
|
|
iso_timestamp_start,
|
|
iso_timestamp_end,
|
|
message,
|
|
}),
|
|
queryFn: ({ signal }) =>
|
|
getWorkerLogs(
|
|
{ projectRef, name, stream, iso_timestamp_start, iso_timestamp_end, message },
|
|
signal
|
|
),
|
|
enabled: IS_PLATFORM && typeof projectRef !== 'undefined' && typeof name !== 'undefined',
|
|
})
|
|
}
|