fix(logs): default unified logs to otel

This commit is contained in:
Jordi Enric committed 2026-10-05 13:50:23 +02:00
1 parent 3fec6bcc16
commit 8d59cf68ab
8 files changed
+68 -127

No files matched your search

@@ -67,10 +67,7 @@ import { useTableRowSelection } from '@/components/ui/DataTable/useTableRowSelec
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
import { useUnifiedLogsChartQuery } from '@/data/logs/unified-logs-chart-query'
import { useUnifiedLogsCountQuery } from '@/data/logs/unified-logs-count-query'
import {
useUnifiedLogsBackend,
useUnifiedLogsInfiniteQuery,
} from '@/data/logs/unified-logs-infinite-query'
import { useUnifiedLogsInfiniteQuery } from '@/data/logs/unified-logs-infinite-query'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useShowMultigresLogs } from '@/hooks/misc/useShowMultigresLogs'
import { useTrack } from '@/lib/telemetry/track'
@@ -96,7 +93,6 @@ export const UnifiedLogs = () => {
useResetFocus()
const { ref: projectRef } = useParams()
const { isReady: isLogsBackendReady } = useUnifiedLogsBackend()
const track = useTrack()
const [search, setSearch] = useQueryStates(SEARCH_PARAMS_PARSER)
const showMultigresLogs = useShowMultigresLogs()
@@ -213,7 +209,6 @@ export const UnifiedLogs = () => {
})
const refetchAllData = () => {
if (!isLogsBackendReady) return
refetchLogs()
refetchCounts()
refetchCharts()
+2 -3
View File
@@ -1,9 +1,8 @@
import { useMutation } from '@tanstack/react-query'
import { useFlag } from 'common'
import { toast } from 'sonner'
import { logsAllEndpointUrl, pickLogsQueryBuilder } from './logs-endpoint'
import { getUnifiedLogsISOStartEnd } from './unified-logs-infinite-query'
import { getUnifiedLogsISOStartEnd, useUnifiedLogsBackend } from './unified-logs-infinite-query'
import { mapUnifiedLogRow, parseUnifiedLogsQueryRows } from './unified-logs.utils'
import { getUnifiedLogsQuery } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.queries'
import { getUnifiedLogsQuery as getUnifiedLogsQueryBq } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.queries.bq'
@@ -58,7 +57,7 @@ export const useGetUnifiedLogsMutation = ({
UseCustomMutationOptions<LogDrainCreateData, ResponseError, getUnifiedLogsVariables>,
'mutationFn'
> = {}) => {
const useOtel = useFlag('otelUnifiedLogs')
const useOtel = useUnifiedLogsBackend()
return useMutation<LogDrainCreateData, ResponseError, getUnifiedLogsVariables>({
mutationFn: (vars) => retrieveUnifiedLogs({ ...vars, useOtel: vars.useOtel ?? useOtel }),
async onSuccess(data, variables, context) {
@@ -1,5 +1,4 @@
import { useQuery } from '@tanstack/react-query'
import { useFlag } from 'common'
import { executeAnalyticsSql } from './execute-analytics-sql'
import { logsKeys } from './keys'
@@ -12,6 +11,7 @@ import { analyticsLiteral as lit, safeSql, type SafeLogSqlFragment } from './saf
import {
getUnifiedLogsISOStartEnd,
UNIFIED_LOGS_QUERY_OPTIONS,
useUnifiedLogsBackend,
} from './unified-logs-infinite-query'
import {
getAuthServiceFlowQuery,
@@ -301,7 +301,7 @@ export const useUnifiedLogInspectionQuery = <TData = UnifiedLogInspectionData>(
...options
}: UseCustomQueryOptions<UnifiedLogInspectionData, UnifiedLogInspectionError, TData> = {}
) => {
const useOtel = !!useFlag('otelUnifiedLogs')
const useOtel = useUnifiedLogsBackend()
return useQuery<UnifiedLogInspectionData, UnifiedLogInspectionError, TData>({
queryKey: [...logsKeys.serviceFlow(projectRef, search, logId), { otel: useOtel }],
queryFn: ({ signal }) =>
@@ -166,14 +166,11 @@ export const useUnifiedLogsChartQuery = <TData = UnifiedLogsChartData>(
...options
}: UseCustomQueryOptions<UnifiedLogsChartData, UnifiedLogsChartError, TData> = {}
) => {
const { isReady, useOtel } = useUnifiedLogsBackend()
const useOtel = useUnifiedLogsBackend()
return useQuery<UnifiedLogsChartData, UnifiedLogsChartError, TData>({
queryKey: [...logsKeys.unifiedLogsChart(projectRef, search), { otel: useOtel, ready: isReady }],
queryFn: ({ signal }) => {
if (!isReady) throw new Error('Unified Logs backend is not ready')
return getUnifiedLogsChart({ projectRef, search, useOtel }, signal)
},
enabled: enabled && isReady && typeof projectRef !== 'undefined',
queryKey: [...logsKeys.unifiedLogsChart(projectRef, search), { otel: useOtel }],
queryFn: ({ signal }) => getUnifiedLogsChart({ projectRef, search, useOtel }, signal),
enabled: enabled && typeof projectRef !== 'undefined',
placeholderData: keepPreviousData,
...UNIFIED_LOGS_QUERY_OPTIONS,
...options,
@@ -90,14 +90,11 @@ export const useUnifiedLogsCountQuery = <TData = UnifiedLogsCountData>(
...options
}: UseCustomQueryOptions<UnifiedLogsCountData, UnifiedLogsCountError, TData> = {}
) => {
const { isReady, useOtel } = useUnifiedLogsBackend()
const useOtel = useUnifiedLogsBackend()
return useQuery<UnifiedLogsCountData, UnifiedLogsCountError, TData>({
queryKey: [...logsKeys.unifiedLogsCount(projectRef, search), { otel: useOtel, ready: isReady }],
queryFn: ({ signal }) => {
if (!isReady) throw new Error('Unified Logs backend is not ready')
return getUnifiedLogsCount({ projectRef, search, useOtel }, signal)
},
enabled: enabled && isReady && typeof projectRef !== 'undefined',
queryKey: [...logsKeys.unifiedLogsCount(projectRef, search), { otel: useOtel }],
queryFn: ({ signal }) => getUnifiedLogsCount({ projectRef, search, useOtel }, signal),
enabled: enabled && typeof projectRef !== 'undefined',
...UNIFIED_LOGS_QUERY_OPTIONS,
...options,
})
@@ -1,5 +1,4 @@
import { useQuery } from '@tanstack/react-query'
import { useFlag } from 'common'
import { executeAnalyticsSql } from './execute-analytics-sql'
import { logsKeys } from './keys'
@@ -9,6 +8,7 @@ import {
getUnifiedLogsISOStartEnd,
UNIFIED_LOGS_QUERY_OPTIONS,
UnifiedLogsVariables,
useUnifiedLogsBackend,
} from './unified-logs-infinite-query'
import { getFacetCountQuery } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.queries'
import {
@@ -65,7 +65,7 @@ export const useUnifiedLogsFacetCountQuery = <TData = UnifiedLogsFacetCountData>
...options
}: UseCustomQueryOptions<UnifiedLogsFacetCountData, UnifiedLogsFacetCountError, TData> = {}
) => {
const useOtel = !!useFlag('otelUnifiedLogs')
const useOtel = useUnifiedLogsBackend()
return useQuery<UnifiedLogsFacetCountData, UnifiedLogsFacetCountError, TData>({
queryKey: [
...logsKeys.unifiedLogsFacetCount(projectRef, facet, facetSearch, search),
@@ -13,7 +13,6 @@ import {
QuerySearchParamsType,
} from '@/components/interfaces/UnifiedLogs/UnifiedLogs.types'
import { handleError } from '@/data/fetchers'
import { IS_PLATFORM } from '@/lib/constants'
import type { ResponseError, UseCustomInfiniteQueryOptions } from '@/types'
const LOGS_PAGE_LIMIT = 50
@@ -30,14 +29,7 @@ export type UnifiedLogsData = any
export type UnifiedLogsError = ResponseError
export type UnifiedLogsVariables = { projectRef?: string; search: QuerySearchParamsType }
export const useUnifiedLogsBackend = () => {
const { hasLoaded, configcat } = useFeatureFlags()
const useOtel = IS_PLATFORM && configcat.otelUnifiedLogs === true
const isReady =
!IS_PLATFORM || (hasLoaded === true && typeof configcat.otelUnifiedLogs === 'boolean')
return { isReady, useOtel }
}
export const useUnifiedLogsBackend = () => useFeatureFlags().configcat.otelUnifiedLogs !== false
export const getUnifiedLogsISOStartEnd = (
search: QuerySearchParamsType,
@@ -162,17 +154,12 @@ export const useUnifiedLogsInfiniteQuery = <TData = UnifiedLogsData>(
PageParam | null
> = {}
) => {
const { isReady, useOtel } = useUnifiedLogsBackend()
const useOtel = useUnifiedLogsBackend()
return useInfiniteQuery({
queryKey: [
...logsKeys.unifiedLogsInfinite(projectRef, search),
{ otel: useOtel, ready: isReady },
],
queryFn: ({ signal, pageParam }) => {
if (!isReady) throw new Error('Unified Logs backend is not ready')
return getUnifiedLogs({ projectRef, search, pageParam, useOtel }, signal)
},
enabled: enabled && isReady && typeof projectRef !== 'undefined',
queryKey: [...logsKeys.unifiedLogsInfinite(projectRef, search), { otel: useOtel }],
queryFn: ({ signal, pageParam }) =>
getUnifiedLogs({ projectRef, search, pageParam, useOtel }, signal),
enabled: enabled && typeof projectRef !== 'undefined',
placeholderData: keepPreviousData,
getPreviousPageParam: (firstPage) => {
if (!firstPage.prevCursor) return null
@@ -9,25 +9,14 @@ import { useUnifiedLogsCountQuery } from './unified-logs-count-query'
import { useUnifiedLogsInfiniteQuery } from './unified-logs-infinite-query'
import type { QuerySearchParamsType } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.types'
const { mockExecuteAnalyticsSql, mockIsPlatform } = vi.hoisted(() => ({
const { mockExecuteAnalyticsSql } = vi.hoisted(() => ({
mockExecuteAnalyticsSql: vi.fn(),
mockIsPlatform: { value: true },
}))
vi.mock('./execute-analytics-sql', () => ({
executeAnalyticsSql: mockExecuteAnalyticsSql,
}))
vi.mock('@/lib/constants', async () => {
const actual = await vi.importActual<Record<string, unknown>>('@/lib/constants')
return {
...actual,
get IS_PLATFORM() {
return mockIsPlatform.value
},
}
})
type FlagState = {
hasLoaded: boolean
otelUnifiedLogs?: boolean
@@ -60,28 +49,29 @@ const createWrapper = (queryClient: QueryClient) => {
}
}
const search: QuerySearchParamsType = {
filter: null,
latency: null,
'timing.dns': null,
'timing.connection': null,
'timing.tls': null,
'timing.ttfb': null,
'timing.transfer': null,
date: null,
sort: null,
size: 40,
start: 0,
direction: 'next',
cursor: new Date(),
id: null,
show_connection_logs: true,
edge_auth: true,
edge_storage: true,
edge_postgrest: true,
user: null,
}
const useUnifiedLogsQueries = () => {
const search: QuerySearchParamsType = {
filter: null,
latency: null,
'timing.dns': null,
'timing.connection': null,
'timing.tls': null,
'timing.ttfb': null,
'timing.transfer': null,
date: null,
sort: null,
size: 40,
start: 0,
direction: 'next',
cursor: new Date(),
id: null,
show_connection_logs: true,
edge_auth: true,
edge_storage: true,
edge_postgrest: true,
user: null,
}
const variables = { projectRef: 'project-ref', search }
return {
chart: useUnifiedLogsChartQuery(variables),
@@ -96,7 +86,6 @@ describe('unified logs queries', () => {
let queryClient: QueryClient
beforeEach(() => {
mockIsPlatform.value = true
flagState.hasLoaded = false
flagState.otelUnifiedLogs = undefined
flagState.unrelatedFlag = undefined
@@ -109,12 +98,10 @@ describe('unified logs queries', () => {
vi.clearAllMocks()
})
test('does not request analytics while flags are pending', async () => {
test('uses OTEL for initial and manual requests while flags are pending', async () => {
const { result } = renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) })
await Promise.resolve()
expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled()
await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3))
await Promise.all([
result.current.chart.refetch(),
@@ -122,23 +109,28 @@ describe('unified logs queries', () => {
result.current.logs.refetch(),
])
expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled()
expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(6)
expect(endpoints()).toEqual(
Array(6).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel')
)
})
test('starts only ClickHouse requests when the flag resolves enabled', async () => {
test('does not start or abort a legacy batch before the flag resolves enabled', async () => {
const { rerender } = renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) })
await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3))
expect(endpoints()).toEqual(
Array(3).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel')
)
flagState.hasLoaded = true
flagState.otelUnifiedLogs = true
rerender()
await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3))
await Promise.resolve()
expect(endpoints()).toEqual([
'/platform/projects/{ref}/analytics/endpoints/logs.all.otel',
'/platform/projects/{ref}/analytics/endpoints/logs.all.otel',
'/platform/projects/{ref}/analytics/endpoints/logs.all.otel',
])
expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3)
expect(mockExecuteAnalyticsSql.mock.calls.every(([args]) => !args.signal.aborted)).toBe(true)
})
test('allows BigQuery requests when the flag resolves disabled', async () => {
@@ -156,52 +148,26 @@ describe('unified logs queries', () => {
])
})
test('does not select BigQuery when ConfigCat fails', async () => {
test('defaults to OTEL when ConfigCat fails', async () => {
flagState.hasLoaded = true
renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) })
await Promise.resolve()
expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled()
await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3))
expect(endpoints()).toEqual(
Array(3).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel')
)
})
test('does not select BigQuery when the loaded flags omit the backend flag', async () => {
test('defaults to OTEL when the loaded flags omit the backend flag', async () => {
flagState.hasLoaded = true
flagState.unrelatedFlag = true
renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) })
await Promise.resolve()
expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled()
})
test('keeps BigQuery available when self-hosted flags are disabled', async () => {
mockIsPlatform.value = false
renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) })
await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3))
expect(endpoints()).toEqual([
'/platform/projects/{ref}/analytics/endpoints/logs.all',
'/platform/projects/{ref}/analytics/endpoints/logs.all',
'/platform/projects/{ref}/analytics/endpoints/logs.all',
])
})
test('does not start and abort a legacy batch before enabled flags resolve', async () => {
const { rerender } = renderHook(useUnifiedLogsQueries, { wrapper: createWrapper(queryClient) })
await Promise.resolve()
expect(mockExecuteAnalyticsSql).not.toHaveBeenCalled()
flagState.hasLoaded = true
flagState.otelUnifiedLogs = true
rerender()
await waitFor(() => expect(mockExecuteAnalyticsSql).toHaveBeenCalledTimes(3))
expect(endpoints()).not.toContain('/platform/projects/{ref}/analytics/endpoints/logs.all')
expect(endpoints()).toEqual(
Array(3).fill('/platform/projects/{ref}/analytics/endpoints/logs.all.otel')
)
})
})