mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 06:09:08 +03:00
added logs and ui to view logs if any
This commit is contained in:
1 parent
01f43acf15
commit
7f77f16fb2
8 files changed
+208
-90
No files matched your search
+3
-1
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user