mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 11:55:05 +03:00
## Problem
Auth log entries store metadata as a stringified JSON object in the
`event_message` field (e.g. `{"msg":"user signed
in","level":"info","status":200,"path":"/auth/v1/token"}`). The
dashboard was rendering this raw JSON string in the event message column
instead of a human-readable message.
## Fix
Added `parseAuthLogEventMessage` utility to `UnifiedLogs.utils.ts` that
JSON-parses the `event_message` value and extracts the `msg` field,
falling back to the `error` field, then the original raw string. The raw
string fallback ensures self-hosted versions with different log formats
continue to render without breaking between releases.
Applied in two places:
- `UnifiedLogs/components/Columns.tsx`: event_message column for
`log_type === 'auth'` rows
- `Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx`:
event_message fallback when `metadata.msg` is absent
## How to test
1. Open the Supabase dashboard and navigate to Logs > Auth Logs
2. Verify that log entries show a plain message (e.g. "login attempt",
"invalid password") in the event message column instead of a raw JSON
string
3. Navigate to the unified logs view and filter to auth logs only
4. Verify the event message column shows the same human-readable
messages
5. Confirm that non-auth log types (edge, postgres, etc.) are unaffected
6. Expected result: auth log entries show readable messages; all other
log sources are unchanged
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Auth log messages now display in a more readable, human-friendly
format by intelligently parsing message data.
* **Refactor**
* Removed tooltip functionality from the pathname column in logs view
for streamlined display.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46147?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
140 lines
4.7 KiB
TypeScript
140 lines
4.7 KiB
TypeScript
import { type Table as TTable } from '@tanstack/react-table'
|
|
import { cn } from 'ui'
|
|
|
|
import { FacetMetadataSchema } from './UnifiedLogs.schema'
|
|
import { LEVELS } from '@/components/ui/DataTable/DataTable.constants'
|
|
|
|
export const logEventBus = {
|
|
listeners: new Map<string, Set<(rowId: string) => void>>(),
|
|
|
|
on(event: 'selectTraceTab', callback: (rowId: string) => void) {
|
|
if (!this.listeners.has(event)) {
|
|
this.listeners.set(event, new Set())
|
|
}
|
|
this.listeners.get(event)?.add(callback)
|
|
return () => this.listeners.get(event)?.delete(callback)
|
|
},
|
|
|
|
emit(event: 'selectTraceTab', rowId: string) {
|
|
this.listeners.get(event)?.forEach((callback) => callback(rowId))
|
|
},
|
|
}
|
|
|
|
export const getFacetedUniqueValues = <TData>(facets?: Record<string, FacetMetadataSchema>) => {
|
|
return (_table: TTable<TData>, columnId: string) => {
|
|
return new Map(facets?.[columnId]?.rows?.map(({ value, total }) => [value, total]) || [])
|
|
}
|
|
}
|
|
|
|
export const getFacetedMinMaxValues = <TData>(facets?: Record<string, FacetMetadataSchema>) => {
|
|
return (_table: TTable<TData>, columnId: string) => {
|
|
const min = facets?.[columnId]?.min
|
|
const max = facets?.[columnId]?.max
|
|
if (typeof min === 'number' && typeof max === 'number') return [min, max]
|
|
if (typeof min === 'number') return [min, min]
|
|
if (typeof max === 'number') return [max, max]
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a unified-logs row's timestamp in epoch milliseconds.
|
|
*
|
|
* The row mapper attaches a pre-parsed `date` (works for both BigQuery
|
|
* microsecond timestamps and OTEL ISO strings); fall back to the raw
|
|
* `timestamp` value when it's a number (older BQ-style microseconds).
|
|
*/
|
|
export function getRowTimestampMs(
|
|
row: { date?: Date | null; timestamp?: number | string | null } | null | undefined
|
|
): number | null {
|
|
if (row?.date instanceof Date) return row.date.getTime()
|
|
if (typeof row?.timestamp === 'number') return row.timestamp / 1000
|
|
return null
|
|
}
|
|
|
|
export const getLevelLabel = (value: (typeof LEVELS)[number]): string => {
|
|
switch (value) {
|
|
case 'success':
|
|
return '2xx'
|
|
case 'warning':
|
|
return '4xx'
|
|
case 'error':
|
|
return '5xx'
|
|
}
|
|
}
|
|
|
|
// Helper function to determine level from HTTP status code
|
|
export const getStatusLevel = (status?: number | string): string => {
|
|
if (!status) return 'success'
|
|
const statusNum = Number(status)
|
|
if (statusNum >= 500) return 'error'
|
|
if (statusNum >= 400) return 'warning'
|
|
if (statusNum >= 300) return 'info' // 3xx redirects are informational
|
|
if (statusNum >= 200) return 'success'
|
|
if (statusNum >= 100) return 'info'
|
|
return 'success'
|
|
}
|
|
|
|
export function getLevelRowClassName(value: (typeof LEVELS)[number]): string {
|
|
switch (value) {
|
|
case 'success':
|
|
return ''
|
|
case 'warning':
|
|
return cn(
|
|
'bg-warning/5 hover:bg-warning/10',
|
|
'data-[state=selected]:bg-warning/20 focus-visible:bg-warning/10',
|
|
'dark:bg-warning/10 dark:hover:bg-warning/20 dark:data-[state=selected]:bg-warning/30 dark:focus-visible:bg-warning/20'
|
|
)
|
|
case 'error':
|
|
return cn(
|
|
'bg-destructive/5 hover:bg-destructive/10',
|
|
'data-[state=selected]:bg-destructive/20 focus-visible:bg-destructive/10',
|
|
'dark:bg-error/10 dark:hover:bg-destructive/20 dark:data-[state=selected]:bg-destructive/30 dark:focus-visible:bg-destructive/20'
|
|
)
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Formats service type strings for display purposes
|
|
* Handles special cases like "edge function" -> "Edge Function"
|
|
* and applies proper capitalization to other service types
|
|
*/
|
|
export function formatServiceTypeForDisplay(serviceType: string): string {
|
|
if (!serviceType) return ''
|
|
|
|
// Handle special cases
|
|
const specialCases: Record<string, string> = {
|
|
'edge function': 'Edge Function',
|
|
postgrest: 'PostgREST',
|
|
postgres: 'Postgres',
|
|
auth: 'Auth',
|
|
storage: 'Storage',
|
|
}
|
|
|
|
return specialCases[serviceType.toLowerCase()] || serviceType
|
|
}
|
|
|
|
/**
|
|
* Parses an auth log event_message that may be a stringified JSON object.
|
|
* Auth log entries store metadata as JSON in event_message (e.g. {"msg":"...","level":"info"}).
|
|
* Extracts the human-readable msg field, falling back to error, then the raw string.
|
|
* The fallback ensures self-hosted versions with different formats still render correctly.
|
|
*/
|
|
export function parseAuthLogEventMessage(value: string | undefined): string | undefined {
|
|
if (!value) return value
|
|
try {
|
|
const parsed = JSON.parse(value)
|
|
if (parsed && typeof parsed === 'object') {
|
|
const msg = parsed.msg
|
|
const err = parsed.error
|
|
if (typeof msg === 'string' && msg.trim()) return msg
|
|
if (typeof err === 'string' && err.trim()) return err
|
|
}
|
|
return value
|
|
} catch {
|
|
return value
|
|
}
|
|
}
|