added logs and ui to view logs if any

This commit is contained in:
Jonathan Summers-Muir committed 2025-05-13 14:17:31 +08:00
1 parent 01f43acf15
commit 7f77f16fb2
8 files changed
+208 -90

No files matched your search

@@ -6,9 +6,11 @@ import { color } from 'framer-motion'
export function DataTableColumnStatusCode({
value,
level,
className,
}: {
value?: number | string
level?: string
className?: string
}) {
// console.log('level', level, level === 'warning' ? value : 'no warning')
const colors = getStatusColor(level)
@@ -17,7 +19,7 @@ export function DataTableColumnStatusCode({
}
return (
<div className="flex items-center relative">
<div className={cn('flex items-center relative', className)}>
<div
className={cn(
'px-1 py-[0.03rem] rounded-md',
@@ -16,7 +16,7 @@ export function ControlsProvider({ children }: { children: React.ReactNode }) {
<div
// REMINDER: access the data-expanded state with tailwind via `group-data-[expanded=true]/controls:block`
// In tailwindcss v4, we could even use `group-data-expanded/controls:block`
className="group/controls"
className="group/controls h-full"
data-expanded={open}
>
{children}
@@ -64,6 +64,7 @@ import { filterFields as defaultFilterFields, sheetFields } from './constants'
import { useParams } from 'common'
import { TraceDetailTab } from './components/trace-detail-tab'
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui/src/components/shadcn/ui/tabs'
import { FunctionLogsTab } from './components/function-logs-tab'
// Debug mode flag - set to true to enable detailed logs
const DEBUG_FILTER_PROCESSING = false
@@ -360,92 +361,133 @@ export function Client() {
</DataTableHeaderLayout>
<Separator />
{/* Use ResizablePanelGroup for the log list and details */}
<div className="flex flex-1 overflow-hidden">
<ResizablePanelGroup direction="horizontal" className="w-full">
<ResizablePanel defaultSize={selectedRowKey ? 60 : 100} minSize={30}>
<div className="h-full overflow-auto">
<DataTableInfinite
columns={columns}
totalRows={totalDBRowCount}
filterRows={filterDBRowCount}
totalRowsFetched={totalFetched}
isFetching={isFetching}
isLoading={isLoading}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
renderLiveRow={(props) => {
if (!liveMode.timestamp) return null
if (props?.row.original.uuid !== liveMode?.row?.uuid) return null
return <LiveRow />
}}
setColumnOrder={setColumnOrder}
setColumnVisibility={setColumnVisibility}
/>
</div>
</ResizablePanel>
{/* <div className="flex flex-1 overflow-hidden"> */}
<ResizablePanelGroup direction="horizontal" className="w-full h-full">
<ResizablePanel
defaultSize={selectedRowKey ? 60 : 100}
minSize={30}
className="h-full"
>
<ResizablePanelGroup key="main-logs" direction="vertical" className="h-full">
<ResizablePanel defaultSize={100} minSize={30}>
<div className="h-full overflow-auto">
<DataTableInfinite
columns={columns}
totalRows={totalDBRowCount}
filterRows={filterDBRowCount}
totalRowsFetched={totalFetched}
isFetching={isFetching}
isLoading={isLoading}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
renderLiveRow={(props) => {
if (!liveMode.timestamp) return null
if (props?.row.original.uuid !== liveMode?.row?.uuid) return null
return <LiveRow />
}}
setColumnOrder={setColumnOrder}
setColumnVisibility={setColumnVisibility}
/>
</div>
</ResizablePanel>
{/* <ResizableHandle withHandle />
<ResizablePanel defaultSize={20} minSize={20} className="bg">
hello world
<pre className="text-xs p-2">
{JSON.stringify(selectedRow?.original, null, 2)}
</pre>
</ResizablePanel> */}
{selectedRow?.original?.logs && selectedRow?.original?.logs?.length > 0 && (
<>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={20} minSize={20}>
<div className="h-full flex flex-col overflow-hidden">
<div className="px-5 py-3 border-b border-border flex justify-between items-center">
<h3 className="text-sm font-medium">
Function Logs (
{selectedRow?.original?.logs &&
Array.isArray(selectedRow?.original?.logs)
? selectedRow?.original?.logs?.length
: 0}
)
</h3>
</div>
<div className="flex-grow overflow-auto">
<FunctionLogsTab logs={selectedRow?.original?.logs} />
</div>
</div>
</ResizablePanel>
</>
)}
</ResizablePanelGroup>
</ResizablePanel>
{selectedRowKey && (
<>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={25} minSize={25}>
<div className="h-full overflow-auto">
<DataTableSheetDetails
title={selectedRow?.original?.pathname}
titleClassName="font-mono"
>
<Tabs
defaultValue="details"
value={activeTab}
onValueChange={setActiveTab}
className="w-full h-full flex flex-col pt-4"
{selectedRowKey && (
<>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={25} minSize={25}>
<ResizablePanelGroup direction="vertical">
<ResizablePanel defaultSize={100} minSize={30}>
<div className="h-full overflow-auto">
<DataTableSheetDetails
title={selectedRow?.original?.pathname}
titleClassName="font-mono"
>
<TabsList className="mb-2 flex gap-3 px-5">
<TabsTrigger value="details">Log Details</TabsTrigger>
<TabsTrigger value="trace">Trace</TabsTrigger>
</TabsList>
<TabsContent
value="details"
className="flex-grow overflow-auto data-[state=active]:flex-grow px-5"
<Tabs
defaultValue="details"
value={activeTab}
onValueChange={setActiveTab}
className="w-full h-full flex flex-col pt-4"
>
<MemoizedDataTableSheetContent
table={table}
data={selectedRow?.original}
filterFields={filterFields}
fields={sheetFields}
metadata={{
totalRows: totalDBRowCount ?? 0,
filterRows: filterDBRowCount ?? 0,
totalRowsFetched: totalFetched ?? 0,
currentPercentiles: metadata?.currentPercentiles ?? ({} as any),
...metadata,
}}
/>
</TabsContent>
<TabsList className="mb-2 flex gap-3 px-5">
<TabsTrigger value="details">Log Details</TabsTrigger>
<TabsTrigger value="trace">Trace</TabsTrigger>
</TabsList>
<TabsContent
value="trace"
className="flex-grow overflow-auto data-[state=active]:flex-grow h-full mt-0 px-5"
>
{selectedRow?.original?.has_trace ? (
<TraceDetailTab id={selectedRow?.original?.id} />
) : (
<div className="flex flex-col gap-2">
<p className="text-sm text-muted-foreground">
No trace found for this log
</p>
</div>
)}
</TabsContent>
</Tabs>
</DataTableSheetDetails>
</div>
</ResizablePanel>
</>
)}
</ResizablePanelGroup>
</div>
<TabsContent
value="details"
className="flex-grow overflow-auto data-[state=active]:flex-grow px-5"
>
<MemoizedDataTableSheetContent
table={table}
data={selectedRow?.original}
filterFields={filterFields}
fields={sheetFields}
metadata={{
totalRows: totalDBRowCount ?? 0,
filterRows: filterDBRowCount ?? 0,
totalRowsFetched: totalFetched ?? 0,
currentPercentiles: metadata?.currentPercentiles ?? ({} as any),
...metadata,
}}
/>
</TabsContent>
<TabsContent
value="trace"
className="flex-grow overflow-auto data-[state=active]:flex-grow h-full mt-0 px-5"
>
{selectedRow?.original?.has_trace ? (
<TraceDetailTab id={selectedRow?.original?.id} />
) : (
<div className="flex flex-col gap-2">
<p className="text-sm text-muted-foreground">
No trace found for this log
</p>
</div>
)}
</TabsContent>
</Tabs>
</DataTableSheetDetails>
</div>
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
</>
)}
</ResizablePanelGroup>
</div>
{/* </div> */}
</DataTableSideBarLayout>
</DataTableProvider>
</>
@@ -0,0 +1,57 @@
import React from 'react'
import { DataTableColumnStatusCode } from 'components/interfaces/DataTableDemo/components/data-table/data-table-column/data-table-column-status-code'
import { HoverCardTimestamp } from 'components/interfaces/DataTableDemo/infinite/_components/hover-card-timestamp'
interface FunctionLogEntry {
id: string
timestamp: string
event_message: string
level: string
event_type: string
}
interface FunctionLogsTabProps {
logs?: FunctionLogEntry[]
}
export const FunctionLogsTab: React.FC<FunctionLogsTabProps> = ({ logs = [] }) => {
if (!logs || logs.length === 0) {
return (
<div className="flex flex-col items-center justify-center h-full p-6">
<p className="text-sm text-muted-foreground">No function logs found</p>
</div>
)
}
return (
<div className="flex flex-col h-full overflow-auto">
<div className="text-sm font-mono">
{logs.map((log) => {
const date = new Date(Number(log.timestamp) / 1000)
// Map the log level to our standard levels
return (
<div key={log.id} className="py-2 border-b border-border last:border-0">
<div className="flex items-start gap-5">
<div className="flex flex-row items-center gap-5">
<HoverCardTimestamp date={date} className="min-w-20 ml-14" />
<DataTableColumnStatusCode
value={log.level}
level={log.level}
className="min-w-20"
/>
<span className="text-foreground">{log.event_type || 'Log'}</span>
</div>
<div className="mt-1 whitespace-pre-wrap break-all pl-2 text-[0.8rem]">
{log.event_message}
</div>
</div>
</div>
)
})}
</div>
</div>
)
}
@@ -170,6 +170,13 @@ export const sheetFields = [
component: (props) => <TraceButton has_trace={Boolean(props.has_trace)} id={props.id} />,
className: 'flex-row items-end justify-end w-full',
},
// {
// id: 'logs',
// label: 'Logs',
// type: 'readonly',
// component: (props) => <pre className="text-xs p-2">{JSON.stringify(props.logs, null, 2)}</pre>,
// skeletonClassName: 'w-full',
// },
{
id: 'status',
label: 'Status',
@@ -250,7 +250,8 @@ export const getEdgeLogsQuery = () => {
edge_logs_request.method as method,
authorization_payload.role as api_role,
COALESCE(sb.auth_user, null) as auth_user,
null as log_count
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
@@ -284,7 +285,8 @@ export const getPostgresLogsQuery = () => {
null as method,
'api_role' as api_role,
null as auth_user,
null as log_count
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
@@ -313,7 +315,8 @@ export const getEdgeFunctionLogsQuery = () => {
fel_request.method as method,
authorization_payload.role as api_role,
COALESCE(sb.auth_user, null) as auth_user,
function_logs_agg.function_log_count as log_count
function_logs_agg.function_log_count as log_count,
function_logs_agg.logs as logs
from function_edge_logs as fel
cross join unnest(metadata) as fel_metadata
cross join unnest(fel_metadata.response) as fel_response
@@ -326,7 +329,8 @@ export const getEdgeFunctionLogsQuery = () => {
SELECT
fl_metadata.execution_id,
COUNT(fl.id) as function_log_count,
ANY_VALUE(fl.event_message) as last_event_message
ANY_VALUE(fl.event_message) as last_event_message,
ARRAY_AGG(STRUCT(fl.id, fl.timestamp, fl.event_message, fl_metadata.level, fl_metadata.event_type)) as logs
FROM function_logs as fl
CROSS JOIN UNNEST(fl.metadata) as fl_metadata
WHERE fl_metadata.execution_id IS NOT NULL
@@ -357,7 +361,8 @@ export const getAuthLogsQuery = () => {
el_in_al_request.method as method,
authorization_payload.role as api_role,
COALESCE(sb.auth_user, null) as auth_user,
null as log_count
null as log_count,
null as logs
from auth_logs as al
cross join unnest(metadata) as al_metadata
left join (
@@ -397,7 +402,8 @@ export const getSupavisorLogsQuery = () => {
null as method,
'api_role' as api_role,
null as auth_user,
null as log_count
null as log_count,
null as logs
from supavisor_logs as svl
cross join unnest(metadata) as svl_metadata
`
@@ -444,7 +450,8 @@ SELECT
method,
api_role,
auth_user,
log_count
log_count,
logs
FROM unified_logs
${finalWhere}
`
@@ -496,6 +496,7 @@ export const dataOptions = (search: SearchParamsType, projectRef: string) => {
latency: row.latency || 0,
log_count: row.log_count || null,
has_trace: Math.random() < 0.5, // Simple 50% random chance
logs: row.logs || [],
}
})
@@ -60,6 +60,7 @@ export const columnSchema = z.object({
log_count: z.number().optional(), // used to count function logs for a given execution_id
// percentile: z.number().optional(),
has_trace: z.boolean().optional(),
logs: z.array(z.any()).optional(), // array of function logs
})
// .merge(timingSchema)
@@ -126,6 +127,7 @@ export const columnFilterSchema = z.object({
.pipe(z.coerce.date().array())
.optional(),
has_trace: z.boolean().optional(),
logs: z.array(z.any()).optional(), // array of function logs
})
export type ColumnFilterSchema = z.infer<typeof columnFilterSchema>