UnifiedLogs.queries.bq.ts 21 KB

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