UnifiedLogs.queries.bq.ts598 lines · main
| 1 | // @ts-nocheck |
| 2 | // Legacy BigQuery unified-logs queries. Kept side-by-side with |
| 3 | // UnifiedLogs.queries.ts (the OTEL/ClickHouse version) so the |
| 4 | // `otelUnifiedLogs` feature flag can route traffic between the two paths |
| 5 | // during the migration. This file should be deleted once the flag is |
| 6 | // removed. |
| 7 | |
| 8 | import dayjs from 'dayjs' |
| 9 | |
| 10 | import { DEFAULT_LOG_TYPES } from './UnifiedLogs.constants' |
| 11 | import { QuerySearchParamsType, SearchParamsType } from './UnifiedLogs.types' |
| 12 | import { |
| 13 | bqIdent, |
| 14 | joinSqlFragments, |
| 15 | analyticsLiteral as lit, |
| 16 | safeSql, |
| 17 | type SafeLogSqlFragment, |
| 18 | } from '@/data/logs/safe-analytics-sql' |
| 19 | |
| 20 | // Pagination and control parameters |
| 21 | const PAGINATION_PARAMS = ['sort', 'start', 'size', 'uuid', 'cursor', 'direction', 'live'] as const |
| 22 | |
| 23 | // Special filter parameters that need custom handling |
| 24 | const SPECIAL_FILTER_PARAMS = ['date'] as const |
| 25 | |
| 26 | // Combined list of all parameters to exclude from standard filtering |
| 27 | const EXCLUDED_QUERY_PARAMS = [...PAGINATION_PARAMS, ...SPECIAL_FILTER_PARAMS] as const |
| 28 | |
| 29 | /** |
| 30 | * Builds WHERE-clause fragments from a search-param map. Identifier-position |
| 31 | * keys are validated via `bqIdent()` (regex allowlist) and value-position |
| 32 | * inputs via `analyticsLiteral` — both throw on disallowed input, in which |
| 33 | * case we drop the predicate rather than emit unsafe SQL. |
| 34 | * |
| 35 | * @param search Search params (URL-derived filter values) |
| 36 | * @param excludeKey Optional key to skip — used by facet-count branches that |
| 37 | * need every filter applied *except* the one being faceted |
| 38 | * @returns Array of SafeLogSqlFragment predicates ready to be AND-joined |
| 39 | */ |
| 40 | const buildConditions = ( |
| 41 | search: QuerySearchParamsType, |
| 42 | excludeKey?: string |
| 43 | ): SafeLogSqlFragment[] => { |
| 44 | const conditions: SafeLogSqlFragment[] = [] |
| 45 | |
| 46 | Object.entries(search).forEach(([key, value]) => { |
| 47 | if (key === excludeKey) return |
| 48 | if ((EXCLUDED_QUERY_PARAMS as readonly string[]).includes(key)) return |
| 49 | |
| 50 | try { |
| 51 | // `key` is interpolated as a column identifier. `bqIdent()` rejects |
| 52 | // anything outside `[A-Za-z_][A-Za-z0-9_]*` (notably no spaces, so a |
| 53 | // crafted URL key like `level OR id IS NOT NULL` is dropped rather |
| 54 | // than emitted into the WHERE clause). |
| 55 | const col = bqIdent(key) |
| 56 | |
| 57 | if (Array.isArray(value) && value.length > 0) { |
| 58 | const inList = joinSqlFragments( |
| 59 | value.map((v) => lit(String(v))), |
| 60 | ',' |
| 61 | ) |
| 62 | conditions.push(safeSql`${col} IN (${inList})`) |
| 63 | return |
| 64 | } |
| 65 | |
| 66 | if (value !== null && value !== undefined) { |
| 67 | if (key === 'host' || key === 'pathname') { |
| 68 | conditions.push(safeSql`${col} LIKE ${lit('%' + String(value) + '%')}`) |
| 69 | } else { |
| 70 | conditions.push(safeSql`${col} = ${lit(String(value))}`) |
| 71 | } |
| 72 | } |
| 73 | } catch { |
| 74 | // bqIdent() or analyticsLiteral() rejected the input — drop the predicate. |
| 75 | } |
| 76 | }) |
| 77 | |
| 78 | return conditions |
| 79 | } |
| 80 | |
| 81 | const whereClause = (conditions: SafeLogSqlFragment[]): SafeLogSqlFragment => |
| 82 | conditions.length > 0 ? safeSql`WHERE ${joinSqlFragments(conditions, ' AND ')}` : safeSql`` |
| 83 | |
| 84 | /** |
| 85 | * Calculates how much the chart start datetime should be offset given the current datetime filter params |
| 86 | * and determines the appropriate bucketing level (minute, hour, day) |
| 87 | * Ported from the older implementation (apps/studio/components/interfaces/Settings/Logs/Logs.utils.ts) |
| 88 | */ |
| 89 | type TruncationLevel = 'MINUTE' | 'HOUR' | 'DAY' |
| 90 | |
| 91 | const TRUNCATION_LEVEL_SQL: Record<TruncationLevel, SafeLogSqlFragment> = { |
| 92 | MINUTE: safeSql`MINUTE`, |
| 93 | HOUR: safeSql`HOUR`, |
| 94 | DAY: safeSql`DAY`, |
| 95 | } |
| 96 | |
| 97 | const calculateChartBucketing = ( |
| 98 | search: SearchParamsType | Record<string, unknown> |
| 99 | ): TruncationLevel => { |
| 100 | // Extract start and end times from the date array if available |
| 101 | const dateRange = (search.date as Array<Date | string | number | null | undefined>) || [] |
| 102 | |
| 103 | // Handle timestamps that could be in various formats |
| 104 | const convertToMillis = (timestamp: Date | string | number | null | undefined) => { |
| 105 | if (!timestamp) return null |
| 106 | // If timestamp is a Date object |
| 107 | if (timestamp instanceof Date) return timestamp.getTime() |
| 108 | |
| 109 | // If timestamp is a string that needs parsing |
| 110 | if (typeof timestamp === 'string') return dayjs(timestamp).valueOf() |
| 111 | |
| 112 | // If timestamp is already a number (unix timestamp) |
| 113 | // Check if microseconds (16 digits) and convert to milliseconds |
| 114 | if (typeof timestamp === 'number') { |
| 115 | const str = timestamp.toString() |
| 116 | if (str.length >= 16) return Math.floor(timestamp / 1000) |
| 117 | return timestamp |
| 118 | } |
| 119 | |
| 120 | return null |
| 121 | } |
| 122 | |
| 123 | let startMillis = convertToMillis(dateRange[0]) |
| 124 | let endMillis = convertToMillis(dateRange[1]) |
| 125 | |
| 126 | // Default values if not set |
| 127 | if (!startMillis) startMillis = dayjs().subtract(1, 'hour').valueOf() |
| 128 | if (!endMillis) endMillis = dayjs().valueOf() |
| 129 | |
| 130 | const startTime = dayjs(startMillis) |
| 131 | const endTime = dayjs(endMillis) |
| 132 | |
| 133 | const hourDiff = endTime.diff(startTime, 'hour') |
| 134 | const dayDiff = endTime.diff(startTime, 'day') |
| 135 | |
| 136 | if (dayDiff >= 2) return 'DAY' |
| 137 | if (hourDiff >= 12) return 'HOUR' |
| 138 | return 'MINUTE' |
| 139 | } |
| 140 | |
| 141 | /** |
| 142 | * Edge logs query fragment |
| 143 | * |
| 144 | * excludes `/rest/` in the path |
| 145 | */ |
| 146 | const getEdgeLogsQuery = (): SafeLogSqlFragment => safeSql` |
| 147 | select |
| 148 | id, |
| 149 | null as source_id, |
| 150 | el.timestamp as timestamp, |
| 151 | 'edge' as log_type, |
| 152 | CAST(edge_logs_response.status_code AS STRING) as status, |
| 153 | CASE |
| 154 | WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 155 | WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 156 | WHEN edge_logs_response.status_code >= 500 THEN 'error' |
| 157 | ELSE 'success' |
| 158 | END as level, |
| 159 | edge_logs_request.path as pathname, |
| 160 | null as event_message, |
| 161 | edge_logs_request.method as method, |
| 162 | null as log_count, |
| 163 | null as logs |
| 164 | from edge_logs as el |
| 165 | cross join unnest(metadata) as edge_logs_metadata |
| 166 | cross join unnest(edge_logs_metadata.request) as edge_logs_request |
| 167 | cross join unnest(edge_logs_metadata.response) as edge_logs_response |
| 168 | |
| 169 | -- ONLY include logs where the path does not include /rest/ |
| 170 | WHERE edge_logs_request.path NOT LIKE '%/rest/%' |
| 171 | AND edge_logs_request.path NOT LIKE '%/storage/%' |
| 172 | ` |
| 173 | |
| 174 | // Postgrest logs — WHERE pathname includes `/rest/` |
| 175 | const getPostgrestLogsQuery = (): SafeLogSqlFragment => safeSql` |
| 176 | select |
| 177 | id, |
| 178 | null as source_id, |
| 179 | el.timestamp as timestamp, |
| 180 | 'postgrest' as log_type, |
| 181 | CAST(edge_logs_response.status_code AS STRING) as status, |
| 182 | CASE |
| 183 | WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 184 | WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 185 | WHEN edge_logs_response.status_code >= 500 THEN 'error' |
| 186 | ELSE 'success' |
| 187 | END as level, |
| 188 | edge_logs_request.path as pathname, |
| 189 | null as event_message, |
| 190 | edge_logs_request.method as method, |
| 191 | null as log_count, |
| 192 | null as logs |
| 193 | from edge_logs as el |
| 194 | cross join unnest(metadata) as edge_logs_metadata |
| 195 | cross join unnest(edge_logs_metadata.request) as edge_logs_request |
| 196 | cross join unnest(edge_logs_metadata.response) as edge_logs_response |
| 197 | |
| 198 | -- ONLY include logs where the path includes /rest/ |
| 199 | WHERE edge_logs_request.path LIKE '%/rest/%' |
| 200 | ` |
| 201 | |
| 202 | /** |
| 203 | * Postgres logs query fragment |
| 204 | */ |
| 205 | const getPostgresLogsQuery = (): SafeLogSqlFragment => safeSql` |
| 206 | select |
| 207 | id, |
| 208 | null as source_id, |
| 209 | pgl.timestamp as timestamp, |
| 210 | 'postgres' as log_type, |
| 211 | CAST(pgl_parsed.sql_state_code AS STRING) as status, |
| 212 | CASE |
| 213 | WHEN pgl_parsed.error_severity = 'LOG' THEN 'success' |
| 214 | WHEN pgl_parsed.error_severity = 'WARNING' THEN 'warning' |
| 215 | WHEN pgl_parsed.error_severity = 'FATAL' THEN 'error' |
| 216 | WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error' |
| 217 | ELSE null |
| 218 | END as level, |
| 219 | null as pathname, |
| 220 | event_message as event_message, |
| 221 | null as method, |
| 222 | null as log_count, |
| 223 | null as logs |
| 224 | from postgres_logs as pgl |
| 225 | cross join unnest(pgl.metadata) as pgl_metadata |
| 226 | cross join unnest(pgl_metadata.parsed) as pgl_parsed |
| 227 | ` |
| 228 | |
| 229 | /** |
| 230 | * Edge function logs query fragment |
| 231 | */ |
| 232 | const getEdgeFunctionLogsQuery = (): SafeLogSqlFragment => safeSql` |
| 233 | select |
| 234 | id, |
| 235 | null as source_id, |
| 236 | fel.timestamp as timestamp, |
| 237 | 'edge function' as log_type, |
| 238 | CAST(fel_response.status_code AS STRING) as status, |
| 239 | CASE |
| 240 | WHEN fel_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 241 | WHEN fel_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 242 | WHEN fel_response.status_code >= 500 THEN 'error' |
| 243 | ELSE 'success' |
| 244 | END as level, |
| 245 | fel_request.pathname as pathname, |
| 246 | COALESCE(function_logs_agg.last_event_message, '') as event_message, |
| 247 | fel_request.method as method, |
| 248 | function_logs_agg.function_log_count as log_count, |
| 249 | null as logs |
| 250 | from function_edge_logs as fel |
| 251 | cross join unnest(metadata) as fel_metadata |
| 252 | cross join unnest(fel_metadata.response) as fel_response |
| 253 | cross join unnest(fel_metadata.request) as fel_request |
| 254 | left join ( |
| 255 | SELECT |
| 256 | fl_metadata.request_id, |
| 257 | COUNT(fl.id) as function_log_count, |
| 258 | ANY_VALUE(fl.event_message) as last_event_message |
| 259 | FROM function_logs as fl |
| 260 | CROSS JOIN UNNEST(fl.metadata) as fl_metadata |
| 261 | WHERE fl_metadata.request_id IS NOT NULL |
| 262 | GROUP BY fl_metadata.request_id |
| 263 | ) as function_logs_agg on fel_metadata.request_id = function_logs_agg.request_id |
| 264 | ` |
| 265 | |
| 266 | /** |
| 267 | * Auth logs query fragment |
| 268 | */ |
| 269 | const getAuthLogsQuery = (): SafeLogSqlFragment => safeSql` |
| 270 | select |
| 271 | el_in_al.id as id, |
| 272 | al.id as source_id, |
| 273 | el_in_al.timestamp as timestamp, |
| 274 | 'auth' as log_type, |
| 275 | CAST(el_in_al_response.status_code AS STRING) as status, |
| 276 | CASE |
| 277 | WHEN el_in_al_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 278 | WHEN el_in_al_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 279 | WHEN el_in_al_response.status_code >= 500 THEN 'error' |
| 280 | ELSE 'success' |
| 281 | END as level, |
| 282 | el_in_al_request.path as pathname, |
| 283 | null as event_message, |
| 284 | el_in_al_request.method as method, |
| 285 | null as log_count, |
| 286 | null as logs |
| 287 | from auth_logs as al |
| 288 | cross join unnest(metadata) as al_metadata |
| 289 | left join ( |
| 290 | edge_logs as el_in_al |
| 291 | cross join unnest (metadata) as el_in_al_metadata |
| 292 | cross join unnest (el_in_al_metadata.response) as el_in_al_response |
| 293 | cross join unnest (el_in_al_response.headers) as el_in_al_response_headers |
| 294 | cross join unnest (el_in_al_metadata.request) as el_in_al_request |
| 295 | ) |
| 296 | on al_metadata.request_id = el_in_al_response_headers.cf_ray |
| 297 | WHERE al_metadata.request_id is not null |
| 298 | ` |
| 299 | |
| 300 | /** |
| 301 | * Briven storage logs query fragment |
| 302 | */ |
| 303 | const getBrivenStorageLogsQuery = (): SafeLogSqlFragment => safeSql` |
| 304 | select |
| 305 | id, |
| 306 | null as source_id, |
| 307 | el.timestamp as timestamp, |
| 308 | 'storage' as log_type, |
| 309 | CAST(edge_logs_response.status_code AS STRING) as status, |
| 310 | CASE |
| 311 | WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 312 | WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 313 | WHEN edge_logs_response.status_code >= 500 THEN 'error' |
| 314 | ELSE 'success' |
| 315 | END as level, |
| 316 | edge_logs_request.path as pathname, |
| 317 | null as event_message, |
| 318 | edge_logs_request.method as method, |
| 319 | null as log_count, |
| 320 | null as logs |
| 321 | from edge_logs as el |
| 322 | cross join unnest(metadata) as edge_logs_metadata |
| 323 | cross join unnest(edge_logs_metadata.request) as edge_logs_request |
| 324 | cross join unnest(edge_logs_metadata.response) as edge_logs_response |
| 325 | -- ONLY include logs where the path includes /storage/ |
| 326 | WHERE edge_logs_request.path LIKE '%/storage/%' |
| 327 | ` |
| 328 | |
| 329 | const LOG_TYPE_QUERIES: Record<string, () => SafeLogSqlFragment> = { |
| 330 | edge: getEdgeLogsQuery, |
| 331 | postgrest: getPostgrestLogsQuery, |
| 332 | postgres: getPostgresLogsQuery, |
| 333 | 'edge function': getEdgeFunctionLogsQuery, |
| 334 | auth: getAuthLogsQuery, |
| 335 | storage: getBrivenStorageLogsQuery, |
| 336 | } |
| 337 | |
| 338 | /** |
| 339 | * Combine the requested log sources to create the unified logs CTE. |
| 340 | * Defaults to postgres + postgrest on first load to reduce query cost. |
| 341 | */ |
| 342 | export const getUnifiedLogsCTE = ( |
| 343 | logTypes: string[] = [...DEFAULT_LOG_TYPES] |
| 344 | ): SafeLogSqlFragment => { |
| 345 | const queries = logTypes |
| 346 | .filter((type) => type in LOG_TYPE_QUERIES) |
| 347 | .map((type) => LOG_TYPE_QUERIES[type]()) |
| 348 | |
| 349 | const effective = |
| 350 | queries.length > 0 ? queries : DEFAULT_LOG_TYPES.map((t) => LOG_TYPE_QUERIES[t]()) |
| 351 | |
| 352 | return safeSql` |
| 353 | WITH unified_logs AS ( |
| 354 | ${joinSqlFragments(effective, ' union all ')} |
| 355 | ) |
| 356 | ` |
| 357 | } |
| 358 | |
| 359 | /** |
| 360 | * Unified logs SQL query |
| 361 | */ |
| 362 | export const getUnifiedLogsQuery = (search: QuerySearchParamsType): SafeLogSqlFragment => { |
| 363 | const conditions = buildConditions(search) |
| 364 | const effectiveLogTypes = search.log_type?.length ? search.log_type : [...DEFAULT_LOG_TYPES] |
| 365 | |
| 366 | return safeSql` |
| 367 | ${getUnifiedLogsCTE(effectiveLogTypes)} |
| 368 | SELECT |
| 369 | id, |
| 370 | source_id, |
| 371 | timestamp, |
| 372 | log_type, |
| 373 | status, |
| 374 | level, |
| 375 | pathname, |
| 376 | event_message, |
| 377 | method, |
| 378 | log_count, |
| 379 | logs |
| 380 | FROM unified_logs |
| 381 | ${whereClause(conditions)} |
| 382 | ` |
| 383 | } |
| 384 | |
| 385 | export const getFacetCountCTE = ({ |
| 386 | search, |
| 387 | facet, |
| 388 | facetSearch, |
| 389 | }: { |
| 390 | search: QuerySearchParamsType |
| 391 | facet: string |
| 392 | facetSearch?: string |
| 393 | }): SafeLogSqlFragment => { |
| 394 | const MAX_FACETS_QUANTITY = 20 |
| 395 | |
| 396 | // `facet` is used both as a column reference and to derive a CTE name; |
| 397 | // quote each appropriately with bqIdent() to reject non-identifier inputs. |
| 398 | const facetCol = bqIdent(facet) |
| 399 | const facetCte = bqIdent(facet + '_count') |
| 400 | const baseConditions = buildConditions(search, facet) |
| 401 | const facetSearchClause = facetSearch |
| 402 | ? safeSql`AND ${facetCol} LIKE ${lit('%' + facetSearch + '%')}` |
| 403 | : safeSql`` |
| 404 | |
| 405 | const where = |
| 406 | baseConditions.length > 0 |
| 407 | ? safeSql`WHERE ${joinSqlFragments(baseConditions, ' AND ')} AND ${facetCol} IS NOT NULL` |
| 408 | : safeSql`WHERE ${facetCol} IS NOT NULL` |
| 409 | |
| 410 | return safeSql` |
| 411 | ${facetCte} AS ( |
| 412 | SELECT ${lit(facet)} as dimension, ${facetCol} as value, COUNT(*) as count |
| 413 | FROM unified_logs |
| 414 | ${where} |
| 415 | ${facetSearchClause} |
| 416 | GROUP BY ${facetCol} |
| 417 | LIMIT ${lit(MAX_FACETS_QUANTITY)} |
| 418 | ) |
| 419 | ` |
| 420 | } |
| 421 | |
| 422 | export const getUnifiedLogsCountCTE = (): SafeLogSqlFragment => safeSql` |
| 423 | WITH unified_logs AS ( |
| 424 | -- Single scan of edge_logs covering edge gateway, postgrest, and storage |
| 425 | select |
| 426 | id, |
| 427 | CASE |
| 428 | WHEN edge_logs_request.path LIKE '%/rest/%' THEN 'postgrest' |
| 429 | WHEN edge_logs_request.path LIKE '%/storage/%' THEN 'storage' |
| 430 | ELSE 'edge' |
| 431 | END as log_type, |
| 432 | CAST(edge_logs_response.status_code AS STRING) as status, |
| 433 | CASE |
| 434 | WHEN edge_logs_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 435 | WHEN edge_logs_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 436 | WHEN edge_logs_response.status_code >= 500 THEN 'error' |
| 437 | ELSE 'success' |
| 438 | END as level, |
| 439 | edge_logs_request.path as pathname, |
| 440 | edge_logs_request.method as method |
| 441 | from edge_logs as el |
| 442 | cross join unnest(metadata) as edge_logs_metadata |
| 443 | cross join unnest(edge_logs_metadata.request) as edge_logs_request |
| 444 | cross join unnest(edge_logs_metadata.response) as edge_logs_response |
| 445 | |
| 446 | union all |
| 447 | |
| 448 | -- Postgres logs |
| 449 | select |
| 450 | id, |
| 451 | 'postgres' as log_type, |
| 452 | CAST(pgl_parsed.sql_state_code AS STRING) as status, |
| 453 | CASE |
| 454 | WHEN pgl_parsed.error_severity = 'LOG' THEN 'success' |
| 455 | WHEN pgl_parsed.error_severity = 'WARNING' THEN 'warning' |
| 456 | WHEN pgl_parsed.error_severity = 'FATAL' THEN 'error' |
| 457 | WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error' |
| 458 | ELSE null |
| 459 | END as level, |
| 460 | null as pathname, |
| 461 | null as method |
| 462 | from postgres_logs as pgl |
| 463 | cross join unnest(pgl.metadata) as pgl_metadata |
| 464 | cross join unnest(pgl_metadata.parsed) as pgl_parsed |
| 465 | |
| 466 | union all |
| 467 | |
| 468 | -- Edge function logs |
| 469 | select |
| 470 | fel.id, |
| 471 | 'edge function' as log_type, |
| 472 | CAST(fel_response.status_code AS STRING) as status, |
| 473 | CASE |
| 474 | WHEN fel_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 475 | WHEN fel_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 476 | WHEN fel_response.status_code >= 500 THEN 'error' |
| 477 | ELSE 'success' |
| 478 | END as level, |
| 479 | fel_request.pathname as pathname, |
| 480 | fel_request.method as method |
| 481 | from function_edge_logs as fel |
| 482 | cross join unnest(metadata) as fel_metadata |
| 483 | cross join unnest(fel_metadata.response) as fel_response |
| 484 | cross join unnest(fel_metadata.request) as fel_request |
| 485 | |
| 486 | union all |
| 487 | |
| 488 | -- Auth logs |
| 489 | select |
| 490 | el_in_al.id as id, |
| 491 | 'auth' as log_type, |
| 492 | CAST(el_in_al_response.status_code AS STRING) as status, |
| 493 | CASE |
| 494 | WHEN el_in_al_response.status_code BETWEEN 200 AND 299 THEN 'success' |
| 495 | WHEN el_in_al_response.status_code BETWEEN 400 AND 499 THEN 'warning' |
| 496 | WHEN el_in_al_response.status_code >= 500 THEN 'error' |
| 497 | ELSE 'success' |
| 498 | END as level, |
| 499 | el_in_al_request.path as pathname, |
| 500 | el_in_al_request.method as method |
| 501 | from auth_logs as al |
| 502 | cross join unnest(metadata) as al_metadata |
| 503 | left join ( |
| 504 | edge_logs as el_in_al |
| 505 | cross join unnest(metadata) as el_in_al_metadata |
| 506 | cross join unnest(el_in_al_metadata.response) as el_in_al_response |
| 507 | cross join unnest(el_in_al_response.headers) as el_in_al_response_headers |
| 508 | cross join unnest(el_in_al_metadata.request) as el_in_al_request |
| 509 | ) |
| 510 | on al_metadata.request_id = el_in_al_response_headers.cf_ray |
| 511 | WHERE al_metadata.request_id is not null |
| 512 | ) |
| 513 | ` |
| 514 | |
| 515 | export const getLogsCountQuery = (search: QuerySearchParamsType): SafeLogSqlFragment => { |
| 516 | const effectiveLogTypes = search.log_type?.length ? search.log_type : [...DEFAULT_LOG_TYPES] |
| 517 | const logTypeConditions = buildConditions(search, 'log_type') |
| 518 | const levelConditions = buildConditions(search, 'level') |
| 519 | const logTypeWhere: SafeLogSqlFragment = |
| 520 | logTypeConditions.length > 0 |
| 521 | ? safeSql`WHERE ${joinSqlFragments(logTypeConditions, ' AND ')}` |
| 522 | : safeSql`WHERE log_type IS NOT NULL` |
| 523 | const levelWhere: SafeLogSqlFragment = |
| 524 | levelConditions.length > 0 |
| 525 | ? safeSql`WHERE ${joinSqlFragments(levelConditions, ' AND ')}` |
| 526 | : safeSql`WHERE level IS NOT NULL` |
| 527 | |
| 528 | return safeSql` |
| 529 | ${getUnifiedLogsCTE(effectiveLogTypes)}, |
| 530 | |
| 531 | -- Single COUNTIF pass for all log_type buckets + total (no GROUP BY / sort needed) |
| 532 | log_type_counts AS ( |
| 533 | SELECT |
| 534 | COUNT(*) AS total, |
| 535 | COUNTIF(log_type = 'edge') AS edge_count, |
| 536 | COUNTIF(log_type = 'postgrest') AS postgrest_count, |
| 537 | COUNTIF(log_type = 'storage') AS storage_count, |
| 538 | COUNTIF(log_type = 'postgres') AS postgres_count, |
| 539 | COUNTIF(log_type = 'edge function') AS edge_function_count, |
| 540 | COUNTIF(log_type = 'auth') AS auth_count |
| 541 | FROM unified_logs |
| 542 | ${logTypeWhere} |
| 543 | ), |
| 544 | |
| 545 | -- Single COUNTIF pass for all level buckets |
| 546 | level_counts AS ( |
| 547 | SELECT |
| 548 | COUNTIF(level = 'success') AS success_count, |
| 549 | COUNTIF(level = 'warning') AS warning_count, |
| 550 | COUNTIF(level = 'error') AS error_count |
| 551 | FROM unified_logs |
| 552 | ${levelWhere} |
| 553 | ), |
| 554 | |
| 555 | -- Variable facets: open-ended values still need GROUP BY |
| 556 | ${getFacetCountCTE({ search, facet: 'method' })}, |
| 557 | ${getFacetCountCTE({ search, facet: 'status' })}, |
| 558 | ${getFacetCountCTE({ search, facet: 'pathname' })} |
| 559 | |
| 560 | SELECT 'total' AS dimension, 'all' AS value, total AS count FROM log_type_counts |
| 561 | UNION ALL SELECT 'log_type', 'edge', edge_count FROM log_type_counts |
| 562 | UNION ALL SELECT 'log_type', 'postgrest', postgrest_count FROM log_type_counts |
| 563 | UNION ALL SELECT 'log_type', 'storage', storage_count FROM log_type_counts |
| 564 | UNION ALL SELECT 'log_type', 'postgres', postgres_count FROM log_type_counts |
| 565 | UNION ALL SELECT 'log_type', 'edge function', edge_function_count FROM log_type_counts |
| 566 | UNION ALL SELECT 'log_type', 'auth', auth_count FROM log_type_counts |
| 567 | UNION ALL SELECT 'level', 'success', success_count FROM level_counts |
| 568 | UNION ALL SELECT 'level', 'warning', warning_count FROM level_counts |
| 569 | UNION ALL SELECT 'level', 'error', error_count FROM level_counts |
| 570 | UNION ALL SELECT dimension, value, count FROM method_count |
| 571 | UNION ALL SELECT dimension, value, count FROM status_count |
| 572 | UNION ALL SELECT dimension, value, count FROM pathname_count |
| 573 | ` |
| 574 | } |
| 575 | |
| 576 | /** |
| 577 | * Enhanced logs chart query with dynamic bucketing based on time range |
| 578 | * Incorporates dynamic bucketing from the older implementation |
| 579 | */ |
| 580 | export const getLogsChartQuery = (search: QuerySearchParamsType): SafeLogSqlFragment => { |
| 581 | const conditions = buildConditions(search) |
| 582 | const truncationLevel = calculateChartBucketing(search) |
| 583 | const effectiveLogTypes = search.log_type?.length ? search.log_type : [...DEFAULT_LOG_TYPES] |
| 584 | |
| 585 | return safeSql` |
| 586 | ${getUnifiedLogsCTE(effectiveLogTypes)} |
| 587 | SELECT |
| 588 | TIMESTAMP_TRUNC(timestamp, ${TRUNCATION_LEVEL_SQL[truncationLevel]}) as time_bucket, |
| 589 | COUNTIF(level = 'success') as success, |
| 590 | COUNTIF(level = 'warning') as warning, |
| 591 | COUNTIF(level = 'error') as error, |
| 592 | COUNT(*) as total_per_bucket |
| 593 | FROM unified_logs |
| 594 | ${whereClause(conditions)} |
| 595 | GROUP BY time_bucket |
| 596 | ORDER BY time_bucket ASC |
| 597 | ` |
| 598 | } |