// @ts-nocheck // Legacy BigQuery unified-logs queries. Kept side-by-side with // UnifiedLogs.queries.ts (the OTEL/ClickHouse version) so the // `otelUnifiedLogs` feature flag can route traffic between the two paths // during the migration. This file should be deleted once the flag is // removed. import dayjs from 'dayjs' import { DEFAULT_LOG_TYPES } from './UnifiedLogs.constants' import { QuerySearchParamsType, SearchParamsType } from './UnifiedLogs.types' import { bqIdent, joinSqlFragments, analyticsLiteral as lit, safeSql, type SafeLogSqlFragment, } from '@/data/logs/safe-analytics-sql' // Pagination and control parameters const PAGINATION_PARAMS = ['sort', 'start', 'size', 'uuid', 'cursor', 'direction', 'live'] as const // Special filter parameters that need custom handling const SPECIAL_FILTER_PARAMS = ['date'] as const // Combined list of all parameters to exclude from standard filtering const EXCLUDED_QUERY_PARAMS = [...PAGINATION_PARAMS, ...SPECIAL_FILTER_PARAMS] as const /** * Builds WHERE-clause fragments from a search-param map. Identifier-position * keys are validated via `bqIdent()` (regex allowlist) and value-position * inputs via `analyticsLiteral` — both throw on disallowed input, in which * case we drop the predicate rather than emit unsafe SQL. * * @param search Search params (URL-derived filter values) * @param excludeKey Optional key to skip — used by facet-count branches that * need every filter applied *except* the one being faceted * @returns Array of SafeLogSqlFragment predicates ready to be AND-joined */ const buildConditions = ( search: QuerySearchParamsType, excludeKey?: string ): SafeLogSqlFragment[] => { const conditions: SafeLogSqlFragment[] = [] Object.entries(search).forEach(([key, value]) => { if (key === excludeKey) return if ((EXCLUDED_QUERY_PARAMS as readonly string[]).includes(key)) return try { // `key` is interpolated as a column identifier. `bqIdent()` rejects // anything outside `[A-Za-z_][A-Za-z0-9_]*` (notably no spaces, so a // crafted URL key like `level OR id IS NOT NULL` is dropped rather // than emitted into the WHERE clause). const col = bqIdent(key) if (Array.isArray(value) && value.length > 0) { const inList = joinSqlFragments( value.map((v) => lit(String(v))), ',' ) conditions.push(safeSql`${col} IN (${inList})`) return } if (value !== null && value !== undefined) { if (key === 'host' || key === 'pathname') { conditions.push(safeSql`${col} LIKE ${lit('%' + String(value) + '%')}`) } else { conditions.push(safeSql`${col} = ${lit(String(value))}`) } } } catch { // bqIdent() or analyticsLiteral() rejected the input — drop the predicate. } }) return conditions } const whereClause = (conditions: SafeLogSqlFragment[]): SafeLogSqlFragment => conditions.length > 0 ? safeSql`WHERE ${joinSqlFragments(conditions, ' AND ')}` : safeSql`` /** * Calculates how much the chart start datetime should be offset given the current datetime filter params * and determines the appropriate bucketing level (minute, hour, day) * Ported from the older implementation (apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts) */ type TruncationLevel = 'MINUTE' | 'HOUR' | 'DAY' const TRUNCATION_LEVEL_SQL: Record = { MINUTE: safeSql`MINUTE`, HOUR: safeSql`HOUR`, DAY: safeSql`DAY`, } const calculateChartBucketing = ( search: SearchParamsType | Record ): TruncationLevel => { // Extract start and end times from the date array if available const dateRange = (search.date as Array) || [] // Handle timestamps that could be in various formats const convertToMillis = (timestamp: Date | string | number | null | undefined) => { if (!timestamp) return null // If timestamp is a Date object if (timestamp instanceof Date) return timestamp.getTime() // If timestamp is a string that needs parsing if (typeof timestamp === 'string') return dayjs(timestamp).valueOf() // If timestamp is already a number (unix timestamp) // Check if microseconds (16 digits) and convert to milliseconds if (typeof timestamp === 'number') { const str = timestamp.toString() if (str.length >= 16) return Math.floor(timestamp / 1000) return timestamp } return null } let startMillis = convertToMillis(dateRange[0]) let endMillis = convertToMillis(dateRange[1]) // Default values if not set if (!startMillis) startMillis = dayjs().subtract(1, 'hour').valueOf() if (!endMillis) endMillis = dayjs().valueOf() const startTime = dayjs(startMillis) const endTime = dayjs(endMillis) const hourDiff = endTime.diff(startTime, 'hour') const dayDiff = endTime.diff(startTime, 'day') if (dayDiff >= 2) return 'DAY' if (hourDiff >= 12) return 'HOUR' return 'MINUTE' } /** * Edge logs query fragment * * excludes `/rest/` in the path */ const getEdgeLogsQuery = (): SafeLogSqlFragment => safeSql` select id, null as source_id, el.timestamp as timestamp, 'edge' as log_type, CAST(edge_logs_response.status_code AS STRING) as status, CASE WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN edge_logs_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, edge_logs_request.path as pathname, null as event_message, edge_logs_request.method as method, null as log_count, null as logs from edge_logs as el cross join unnest(metadata) as edge_logs_metadata cross join unnest(edge_logs_metadata.request) as edge_logs_request cross join unnest(edge_logs_metadata.response) as edge_logs_response -- ONLY include logs where the path does not include /rest/ WHERE edge_logs_request.path NOT LIKE '%/rest/%' AND edge_logs_request.path NOT LIKE '%/storage/%' ` // Postgrest logs — WHERE pathname includes `/rest/` const getPostgrestLogsQuery = (): SafeLogSqlFragment => safeSql` select id, null as source_id, el.timestamp as timestamp, 'postgrest' as log_type, CAST(edge_logs_response.status_code AS STRING) as status, CASE WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN edge_logs_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, edge_logs_request.path as pathname, null as event_message, edge_logs_request.method as method, null as log_count, null as logs from edge_logs as el cross join unnest(metadata) as edge_logs_metadata cross join unnest(edge_logs_metadata.request) as edge_logs_request cross join unnest(edge_logs_metadata.response) as edge_logs_response -- ONLY include logs where the path includes /rest/ WHERE edge_logs_request.path LIKE '%/rest/%' ` /** * Postgres logs query fragment */ const getPostgresLogsQuery = (): SafeLogSqlFragment => safeSql` select id, null as source_id, pgl.timestamp as timestamp, 'postgres' as log_type, CAST(pgl_parsed.sql_state_code AS STRING) as status, CASE WHEN pgl_parsed.error_severity = 'LOG' THEN 'success' WHEN pgl_parsed.error_severity = 'WARNING' THEN 'warning' WHEN pgl_parsed.error_severity = 'FATAL' THEN 'error' WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error' ELSE null END as level, null as pathname, event_message as event_message, null as method, null as log_count, null as logs from postgres_logs as pgl cross join unnest(pgl.metadata) as pgl_metadata cross join unnest(pgl_metadata.parsed) as pgl_parsed ` /** * Edge function logs query fragment */ const getEdgeFunctionLogsQuery = (): SafeLogSqlFragment => safeSql` select id, null as source_id, fel.timestamp as timestamp, 'edge function' as log_type, CAST(fel_response.status_code AS STRING) as status, CASE WHEN fel_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN fel_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN fel_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, fel_request.pathname as pathname, COALESCE(function_logs_agg.last_event_message, '') as event_message, fel_request.method as method, function_logs_agg.function_log_count as log_count, null as logs from function_edge_logs as fel cross join unnest(metadata) as fel_metadata cross join unnest(fel_metadata.response) as fel_response cross join unnest(fel_metadata.request) as fel_request left join ( SELECT fl_metadata.request_id, COUNT(fl.id) as function_log_count, ANY_VALUE(fl.event_message) as last_event_message FROM function_logs as fl CROSS JOIN UNNEST(fl.metadata) as fl_metadata WHERE fl_metadata.request_id IS NOT NULL GROUP BY fl_metadata.request_id ) as function_logs_agg on fel_metadata.request_id = function_logs_agg.request_id ` /** * Auth logs query fragment */ const getAuthLogsQuery = (): SafeLogSqlFragment => safeSql` select el_in_al.id as id, al.id as source_id, el_in_al.timestamp as timestamp, 'auth' as log_type, CAST(el_in_al_response.status_code AS STRING) as status, CASE WHEN el_in_al_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN el_in_al_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN el_in_al_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, el_in_al_request.path as pathname, null as event_message, el_in_al_request.method as method, null as log_count, null as logs from auth_logs as al cross join unnest(metadata) as al_metadata left join ( edge_logs as el_in_al cross join unnest (metadata) as el_in_al_metadata cross join unnest (el_in_al_metadata.response) as el_in_al_response cross join unnest (el_in_al_response.headers) as el_in_al_response_headers cross join unnest (el_in_al_metadata.request) as el_in_al_request ) on al_metadata.request_id = el_in_al_response_headers.cf_ray WHERE al_metadata.request_id is not null ` /** * Briven storage logs query fragment */ const getBrivenStorageLogsQuery = (): SafeLogSqlFragment => safeSql` select id, null as source_id, el.timestamp as timestamp, 'storage' as log_type, CAST(edge_logs_response.status_code AS STRING) as status, CASE WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN edge_logs_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, edge_logs_request.path as pathname, null as event_message, edge_logs_request.method as method, null as log_count, null as logs from edge_logs as el cross join unnest(metadata) as edge_logs_metadata cross join unnest(edge_logs_metadata.request) as edge_logs_request cross join unnest(edge_logs_metadata.response) as edge_logs_response -- ONLY include logs where the path includes /storage/ WHERE edge_logs_request.path LIKE '%/storage/%' ` const LOG_TYPE_QUERIES: Record SafeLogSqlFragment> = { edge: getEdgeLogsQuery, postgrest: getPostgrestLogsQuery, postgres: getPostgresLogsQuery, 'edge function': getEdgeFunctionLogsQuery, auth: getAuthLogsQuery, storage: getBrivenStorageLogsQuery, } /** * Combine the requested log sources to create the unified logs CTE. * Defaults to postgres + postgrest on first load to reduce query cost. */ export const getUnifiedLogsCTE = ( logTypes: string[] = [...DEFAULT_LOG_TYPES] ): SafeLogSqlFragment => { const queries = logTypes .filter((type) => type in LOG_TYPE_QUERIES) .map((type) => LOG_TYPE_QUERIES[type]()) const effective = queries.length > 0 ? queries : DEFAULT_LOG_TYPES.map((t) => LOG_TYPE_QUERIES[t]()) return safeSql` WITH unified_logs AS ( ${joinSqlFragments(effective, ' union all ')} ) ` } /** * Unified logs SQL query */ export const getUnifiedLogsQuery = (search: QuerySearchParamsType): SafeLogSqlFragment => { const conditions = buildConditions(search) const effectiveLogTypes = search.log_type?.length ? search.log_type : [...DEFAULT_LOG_TYPES] return safeSql` ${getUnifiedLogsCTE(effectiveLogTypes)} SELECT id, source_id, timestamp, log_type, status, level, pathname, event_message, method, log_count, logs FROM unified_logs ${whereClause(conditions)} ` } export const getFacetCountCTE = ({ search, facet, facetSearch, }: { search: QuerySearchParamsType facet: string facetSearch?: string }): SafeLogSqlFragment => { const MAX_FACETS_QUANTITY = 20 // `facet` is used both as a column reference and to derive a CTE name; // quote each appropriately with bqIdent() to reject non-identifier inputs. const facetCol = bqIdent(facet) const facetCte = bqIdent(facet + '_count') const baseConditions = buildConditions(search, facet) const facetSearchClause = facetSearch ? safeSql`AND ${facetCol} LIKE ${lit('%' + facetSearch + '%')}` : safeSql`` const where = baseConditions.length > 0 ? safeSql`WHERE ${joinSqlFragments(baseConditions, ' AND ')} AND ${facetCol} IS NOT NULL` : safeSql`WHERE ${facetCol} IS NOT NULL` return safeSql` ${facetCte} AS ( SELECT ${lit(facet)} as dimension, ${facetCol} as value, COUNT(*) as count FROM unified_logs ${where} ${facetSearchClause} GROUP BY ${facetCol} LIMIT ${lit(MAX_FACETS_QUANTITY)} ) ` } export const getUnifiedLogsCountCTE = (): SafeLogSqlFragment => safeSql` WITH unified_logs AS ( -- Single scan of edge_logs covering edge gateway, postgrest, and storage select id, CASE WHEN edge_logs_request.path LIKE '%/rest/%' THEN 'postgrest' WHEN edge_logs_request.path LIKE '%/storage/%' THEN 'storage' ELSE 'edge' END as log_type, CAST(edge_logs_response.status_code AS STRING) as status, CASE WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN edge_logs_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, edge_logs_request.path as pathname, edge_logs_request.method as method from edge_logs as el cross join unnest(metadata) as edge_logs_metadata cross join unnest(edge_logs_metadata.request) as edge_logs_request cross join unnest(edge_logs_metadata.response) as edge_logs_response union all -- Postgres logs select id, 'postgres' as log_type, CAST(pgl_parsed.sql_state_code AS STRING) as status, CASE WHEN pgl_parsed.error_severity = 'LOG' THEN 'success' WHEN pgl_parsed.error_severity = 'WARNING' THEN 'warning' WHEN pgl_parsed.error_severity = 'FATAL' THEN 'error' WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error' ELSE null END as level, null as pathname, null as method from postgres_logs as pgl cross join unnest(pgl.metadata) as pgl_metadata cross join unnest(pgl_metadata.parsed) as pgl_parsed union all -- Edge function logs select fel.id, 'edge function' as log_type, CAST(fel_response.status_code AS STRING) as status, CASE WHEN fel_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN fel_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN fel_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, fel_request.pathname as pathname, fel_request.method as method from function_edge_logs as fel cross join unnest(metadata) as fel_metadata cross join unnest(fel_metadata.response) as fel_response cross join unnest(fel_metadata.request) as fel_request union all -- Auth logs select el_in_al.id as id, 'auth' as log_type, CAST(el_in_al_response.status_code AS STRING) as status, CASE WHEN el_in_al_response.status_code BETWEEN 200 AND 299 THEN 'success' WHEN el_in_al_response.status_code BETWEEN 400 AND 499 THEN 'warning' WHEN el_in_al_response.status_code >= 500 THEN 'error' ELSE 'success' END as level, el_in_al_request.path as pathname, el_in_al_request.method as method from auth_logs as al cross join unnest(metadata) as al_metadata left join ( edge_logs as el_in_al cross join unnest(metadata) as el_in_al_metadata cross join unnest(el_in_al_metadata.response) as el_in_al_response cross join unnest(el_in_al_response.headers) as el_in_al_response_headers cross join unnest(el_in_al_metadata.request) as el_in_al_request ) on al_metadata.request_id = el_in_al_response_headers.cf_ray WHERE al_metadata.request_id is not null ) ` export const getLogsCountQuery = (search: QuerySearchParamsType): SafeLogSqlFragment => { const effectiveLogTypes = search.log_type?.length ? search.log_type : [...DEFAULT_LOG_TYPES] const logTypeConditions = buildConditions(search, 'log_type') const levelConditions = buildConditions(search, 'level') const logTypeWhere: SafeLogSqlFragment = logTypeConditions.length > 0 ? safeSql`WHERE ${joinSqlFragments(logTypeConditions, ' AND ')}` : safeSql`WHERE log_type IS NOT NULL` const levelWhere: SafeLogSqlFragment = levelConditions.length > 0 ? safeSql`WHERE ${joinSqlFragments(levelConditions, ' AND ')}` : safeSql`WHERE level IS NOT NULL` return safeSql` ${getUnifiedLogsCTE(effectiveLogTypes)}, -- Single COUNTIF pass for all log_type buckets + total (no GROUP BY / sort needed) log_type_counts AS ( SELECT COUNT(*) AS total, COUNTIF(log_type = 'edge') AS edge_count, COUNTIF(log_type = 'postgrest') AS postgrest_count, COUNTIF(log_type = 'storage') AS storage_count, COUNTIF(log_type = 'postgres') AS postgres_count, COUNTIF(log_type = 'edge function') AS edge_function_count, COUNTIF(log_type = 'auth') AS auth_count FROM unified_logs ${logTypeWhere} ), -- Single COUNTIF pass for all level buckets level_counts AS ( SELECT COUNTIF(level = 'success') AS success_count, COUNTIF(level = 'warning') AS warning_count, COUNTIF(level = 'error') AS error_count FROM unified_logs ${levelWhere} ), -- Variable facets: open-ended values still need GROUP BY ${getFacetCountCTE({ search, facet: 'method' })}, ${getFacetCountCTE({ search, facet: 'status' })}, ${getFacetCountCTE({ search, facet: 'pathname' })} SELECT 'total' AS dimension, 'all' AS value, total AS count FROM log_type_counts UNION ALL SELECT 'log_type', 'edge', edge_count FROM log_type_counts UNION ALL SELECT 'log_type', 'postgrest', postgrest_count FROM log_type_counts UNION ALL SELECT 'log_type', 'storage', storage_count FROM log_type_counts UNION ALL SELECT 'log_type', 'postgres', postgres_count FROM log_type_counts UNION ALL SELECT 'log_type', 'edge function', edge_function_count FROM log_type_counts UNION ALL SELECT 'log_type', 'auth', auth_count FROM log_type_counts UNION ALL SELECT 'level', 'success', success_count FROM level_counts UNION ALL SELECT 'level', 'warning', warning_count FROM level_counts UNION ALL SELECT 'level', 'error', error_count FROM level_counts UNION ALL SELECT dimension, value, count FROM method_count UNION ALL SELECT dimension, value, count FROM status_count UNION ALL SELECT dimension, value, count FROM pathname_count ` } /** * Enhanced logs chart query with dynamic bucketing based on time range * Incorporates dynamic bucketing from the older implementation */ export const getLogsChartQuery = (search: QuerySearchParamsType): SafeLogSqlFragment => { const conditions = buildConditions(search) const truncationLevel = calculateChartBucketing(search) const effectiveLogTypes = search.log_type?.length ? search.log_type : [...DEFAULT_LOG_TYPES] return safeSql` ${getUnifiedLogsCTE(effectiveLogTypes)} SELECT TIMESTAMP_TRUNC(timestamp, ${TRUNCATION_LEVEL_SQL[truncationLevel]}) as time_bucket, COUNTIF(level = 'success') as success, COUNTIF(level = 'warning') as warning, COUNTIF(level = 'error') as error, COUNT(*) as total_per_bucket FROM unified_logs ${whereClause(conditions)} GROUP BY time_bucket ORDER BY time_bucket ASC ` }