| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- // @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<TruncationLevel, SafeLogSqlFragment> = {
- MINUTE: safeSql`MINUTE`,
- HOUR: safeSql`HOUR`,
- DAY: safeSql`DAY`,
- }
- const calculateChartBucketing = (
- search: SearchParamsType | Record<string, unknown>
- ): TruncationLevel => {
- // Extract start and end times from the date array if available
- const dateRange = (search.date as Array<Date | string | number | null | undefined>) || []
- // 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<string, () => 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
- `
- }
|