import { useQuery } from '@tanstack/react-query'; import { config } from '../../config'; import { useAuthStore } from '../../auth/auth-store'; import { useRefreshInterval } from './use-refresh-interval'; import { useGlobalFilters } from '@cameleer/design-system'; import { useEnvironmentStore } from '../environment-store'; export interface LogEntryResponse { timestamp: string; level: string; loggerName: string | null; message: string; threadName: string | null; stackTrace: string | null; exchangeId: string | null; instanceId: string | null; application: string | null; mdc: Record | null; source: string | null; } export interface LogSearchPageResponse { data: LogEntryResponse[]; nextCursor: string | null; hasMore: boolean; levelCounts: Record; } export interface LogSearchParams { q?: string; level?: string; application?: string; agentId?: string; source?: string; environment?: string; exchangeId?: string; logger?: string; from?: string; to?: string; cursor?: string; limit?: number; sort?: 'asc' | 'desc'; } async function fetchLogs(params: LogSearchParams): Promise { const token = useAuthStore.getState().accessToken; const urlParams = new URLSearchParams(); if (params.q) urlParams.set('q', params.q); if (params.level) urlParams.set('level', params.level); if (params.application) urlParams.set('application', params.application); if (params.agentId) urlParams.set('agentId', params.agentId); if (params.source) urlParams.set('source', params.source); if (params.environment) urlParams.set('environment', params.environment); if (params.exchangeId) urlParams.set('exchangeId', params.exchangeId); if (params.logger) urlParams.set('logger', params.logger); if (params.from) urlParams.set('from', params.from); if (params.to) urlParams.set('to', params.to); if (params.cursor) urlParams.set('cursor', params.cursor); if (params.limit) urlParams.set('limit', String(params.limit)); if (params.sort) urlParams.set('sort', params.sort); const res = await fetch(`${config.apiBaseUrl}/logs?${urlParams}`, { headers: { Authorization: `Bearer ${token}`, 'X-Cameleer-Protocol-Version': '1', }, }); if (!res.ok) throw new Error('Failed to load logs'); return res.json() as Promise; } /** * Primary log search hook with cursor pagination and level counts. */ export function useLogs( params: LogSearchParams, options?: { enabled?: boolean; refetchInterval?: number | false }, ) { const defaultRefetch = useRefreshInterval(15_000); return useQuery({ queryKey: ['logs', params], queryFn: () => fetchLogs(params), enabled: options?.enabled ?? true, placeholderData: (prev) => prev, refetchInterval: options?.refetchInterval ?? defaultRefetch, staleTime: 300, }); } /** * Backward-compatible wrapper for existing consumers (LogTab, AgentHealth, AgentInstance). * Returns the same shape they expect: data is the LogEntryResponse[] (unwrapped from the page response). */ export function useApplicationLogs( application?: string, agentId?: string, options?: { limit?: number; toOverride?: string; exchangeId?: string; source?: string }, ) { const refetchInterval = useRefreshInterval(15_000); const { timeRange } = useGlobalFilters(); const selectedEnv = useEnvironmentStore((s) => s.environment); const to = options?.toOverride ?? timeRange.end.toISOString(); const useTimeRange = !options?.exchangeId; const params: LogSearchParams = { application: application || undefined, agentId: agentId || undefined, source: options?.source || undefined, environment: selectedEnv || undefined, exchangeId: options?.exchangeId || undefined, from: useTimeRange ? timeRange.start.toISOString() : undefined, to: useTimeRange ? to : undefined, limit: options?.limit, }; const query = useQuery({ queryKey: ['logs', 'compat', application, agentId, selectedEnv, useTimeRange ? timeRange.start.toISOString() : null, useTimeRange ? to : null, options?.limit, options?.exchangeId, options?.source], queryFn: () => fetchLogs(params), enabled: !!application, placeholderData: (prev) => prev, refetchInterval, }); // Unwrap: existing consumers expect data to be LogEntryResponse[] directly return { ...query, data: query.data?.data ?? (undefined as LogEntryResponse[] | undefined), }; } /** * Fetches container startup logs for a deployment. * Polls every 3s while the deployment is STARTING, stops when RUNNING/FAILED. */ export function useStartupLogs( application: string | undefined, environment: string | undefined, deployCreatedAt: string | undefined, isStarting: boolean, ) { const params: LogSearchParams = { application: application || undefined, environment: environment || undefined, source: 'container', from: deployCreatedAt || undefined, sort: 'asc', limit: 500, }; return useLogs(params, { enabled: !!application && !!deployCreatedAt, refetchInterval: isStarting ? 3_000 : false, }); }