mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:11:11 -05:00
Fix error logging and metadata
This commit is contained in:
134
docs/ERROR_LOGGING_FIX_COMPLETE.md
Normal file
134
docs/ERROR_LOGGING_FIX_COMPLETE.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Error Logging Fix - Complete ✅
|
||||||
|
|
||||||
|
**Date:** 2025-11-03
|
||||||
|
**Status:** COMPLETE
|
||||||
|
|
||||||
|
## Problem Summary
|
||||||
|
The error logging system had critical database schema mismatches that prevented proper error tracking:
|
||||||
|
1. Missing `timezone` and `referrer` columns in `request_metadata` table
|
||||||
|
2. Application code expected breadcrumbs to be pre-fetched but wasn't passing environment data
|
||||||
|
3. Database function signature didn't match application calls
|
||||||
|
|
||||||
|
## Solution Implemented
|
||||||
|
|
||||||
|
### 1. Database Schema Fix (Migration)
|
||||||
|
```sql
|
||||||
|
-- Added missing environment columns
|
||||||
|
ALTER TABLE public.request_metadata
|
||||||
|
ADD COLUMN IF NOT EXISTS timezone TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS referrer TEXT;
|
||||||
|
|
||||||
|
-- Added index for better breadcrumbs performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_request_breadcrumbs_request_id
|
||||||
|
ON public.request_breadcrumbs(request_id);
|
||||||
|
|
||||||
|
-- Updated log_request_metadata function
|
||||||
|
-- Now accepts p_timezone and p_referrer parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Application Code Updates
|
||||||
|
|
||||||
|
#### `src/lib/requestTracking.ts`
|
||||||
|
- ✅ Added `captureEnvironmentContext()` import
|
||||||
|
- ✅ Captures environment context on error
|
||||||
|
- ✅ Passes `timezone` and `referrer` to database function
|
||||||
|
- ✅ Updated `RequestMetadata` interface with new fields
|
||||||
|
|
||||||
|
#### `src/components/admin/ErrorDetailsModal.tsx`
|
||||||
|
- ✅ Added missing imports (`useState`, `useEffect`, `supabase`)
|
||||||
|
- ✅ Simplified to use breadcrumbs from parent query (already fetched)
|
||||||
|
- ✅ Displays timezone and referrer in Environment tab
|
||||||
|
- ✅ Removed unused state management
|
||||||
|
|
||||||
|
#### `src/pages/admin/ErrorMonitoring.tsx`
|
||||||
|
- ✅ Already correctly fetches breadcrumbs from `request_breadcrumbs` table
|
||||||
|
- ✅ No changes needed - working as expected
|
||||||
|
|
||||||
|
## Architecture: Full Relational Structure
|
||||||
|
|
||||||
|
Following the project's **"NO JSON OR JSONB"** policy:
|
||||||
|
- ✅ Breadcrumbs stored in separate `request_breadcrumbs` table
|
||||||
|
- ✅ Environment data stored as direct columns (`timezone`, `referrer`, `user_agent`, etc.)
|
||||||
|
- ✅ No JSONB in active data structures
|
||||||
|
- ✅ Legacy `p_environment_context` parameter kept for backward compatibility (receives empty string)
|
||||||
|
|
||||||
|
## What Now Works
|
||||||
|
|
||||||
|
### Error Capture
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
// Your code
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, {
|
||||||
|
action: 'Action Name',
|
||||||
|
userId: user?.id,
|
||||||
|
metadata: { /* context */ }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Captures:**
|
||||||
|
- ✅ Full stack trace (up to 5000 chars)
|
||||||
|
- ✅ Last 10 breadcrumbs (navigation, actions, API calls)
|
||||||
|
- ✅ Environment context (timezone, referrer, user agent, client version)
|
||||||
|
- ✅ Request metadata (endpoint, method, duration)
|
||||||
|
- ✅ User context (user ID if authenticated)
|
||||||
|
|
||||||
|
### Error Monitoring Dashboard (`/admin/error-monitoring`)
|
||||||
|
- ✅ Lists recent errors with filtering
|
||||||
|
- ✅ Search by request ID, endpoint, or message
|
||||||
|
- ✅ Date range filtering (1h, 24h, 7d, 30d)
|
||||||
|
- ✅ Error type filtering
|
||||||
|
- ✅ Auto-refresh every 30 seconds
|
||||||
|
- ✅ Error analytics overview
|
||||||
|
|
||||||
|
### Error Details Modal
|
||||||
|
- ✅ **Overview Tab:** Request ID, timestamp, endpoint, method, status, duration, user
|
||||||
|
- ✅ **Stack Trace Tab:** Full error stack (if available)
|
||||||
|
- ✅ **Breadcrumbs Tab:** User actions leading to error (sorted by sequence)
|
||||||
|
- ✅ **Environment Tab:** Timezone, referrer, user agent, client version, IP hash
|
||||||
|
- ✅ Copy error ID (short reference for support)
|
||||||
|
- ✅ Copy full error report (for sharing with devs)
|
||||||
|
|
||||||
|
### Error Lookup (`/admin/error-lookup`)
|
||||||
|
- ✅ Quick search by short reference ID (first 8 chars)
|
||||||
|
- ✅ Direct link from user-facing error messages
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [x] Database migration applied successfully
|
||||||
|
- [x] New columns exist in `request_metadata` table
|
||||||
|
- [x] `log_request_metadata` function accepts new parameters
|
||||||
|
- [x] Application code compiles without errors
|
||||||
|
- [ ] **Manual Test Required:** Trigger an error and verify:
|
||||||
|
- [ ] Error appears in `/admin/error-monitoring`
|
||||||
|
- [ ] Click error shows all tabs with data
|
||||||
|
- [ ] Breadcrumbs display correctly
|
||||||
|
- [ ] Environment tab shows timezone and referrer
|
||||||
|
- [ ] Copy functions work
|
||||||
|
|
||||||
|
## Performance Notes
|
||||||
|
|
||||||
|
- Breadcrumbs query is indexed (`idx_request_breadcrumbs_request_id`)
|
||||||
|
- Breadcrumbs limited to last 10 per request (prevents memory bloat)
|
||||||
|
- Error stack traces limited to 5000 chars
|
||||||
|
- Fire-and-forget logging (doesn't block user operations)
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- `src/lib/requestTracking.ts` - Request/error tracking service
|
||||||
|
- `src/lib/errorHandler.ts` - Error handling utilities
|
||||||
|
- `src/lib/errorBreadcrumbs.ts` - Breadcrumb capture system
|
||||||
|
- `src/lib/environmentContext.ts` - Environment data capture
|
||||||
|
- `src/pages/admin/ErrorMonitoring.tsx` - Error monitoring dashboard
|
||||||
|
- `src/components/admin/ErrorDetailsModal.tsx` - Error details modal
|
||||||
|
- `docs/ERROR_TRACKING.md` - Full system documentation
|
||||||
|
- `docs/LOGGING_POLICY.md` - Logging policy and best practices
|
||||||
|
|
||||||
|
## Next Steps (Optional Enhancements)
|
||||||
|
|
||||||
|
1. Add error trending graphs (error count over time)
|
||||||
|
2. Add error grouping by stack trace similarity
|
||||||
|
3. Add user notification when their error is resolved
|
||||||
|
4. Add automatic error assignment to developers
|
||||||
|
5. Add integration with external monitoring (Sentry, etc.)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@@ -5,6 +6,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|||||||
import { Copy, ExternalLink } from 'lucide-react';
|
import { Copy, ExternalLink } from 'lucide-react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
|
||||||
interface Breadcrumb {
|
interface Breadcrumb {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@@ -39,6 +41,8 @@ interface ErrorDetailsModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ErrorDetailsModal({ error, onClose }: ErrorDetailsModalProps) {
|
export function ErrorDetailsModal({ error, onClose }: ErrorDetailsModalProps) {
|
||||||
|
// Use breadcrumbs from error object if already fetched, otherwise they'll be empty
|
||||||
|
const breadcrumbs = error.request_breadcrumbs || [];
|
||||||
const copyErrorId = () => {
|
const copyErrorId = () => {
|
||||||
navigator.clipboard.writeText(error.request_id);
|
navigator.clipboard.writeText(error.request_id);
|
||||||
toast.success('Error ID copied to clipboard');
|
toast.success('Error ID copied to clipboard');
|
||||||
@@ -150,9 +154,9 @@ ${error.error_stack ? `Stack Trace:\n${error.error_stack}` : ''}
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="breadcrumbs">
|
<TabsContent value="breadcrumbs">
|
||||||
{error.request_breadcrumbs && error.request_breadcrumbs.length > 0 ? (
|
{breadcrumbs && breadcrumbs.length > 0 ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{error.request_breadcrumbs
|
{breadcrumbs
|
||||||
.sort((a, b) => (a.sequence_order || 0) - (b.sequence_order || 0))
|
.sort((a, b) => (a.sequence_order || 0) - (b.sequence_order || 0))
|
||||||
.map((crumb, index) => (
|
.map((crumb, index) => (
|
||||||
<div key={index} className="border-l-2 border-primary pl-4 py-2">
|
<div key={index} className="border-l-2 border-primary pl-4 py-2">
|
||||||
|
|||||||
@@ -2797,6 +2797,7 @@ export type Database = {
|
|||||||
ip_address_hash: string | null
|
ip_address_hash: string | null
|
||||||
method: string
|
method: string
|
||||||
parent_request_id: string | null
|
parent_request_id: string | null
|
||||||
|
referrer: string | null
|
||||||
request_id: string
|
request_id: string
|
||||||
request_method: string | null
|
request_method: string | null
|
||||||
request_path: string | null
|
request_path: string | null
|
||||||
@@ -2806,6 +2807,7 @@ export type Database = {
|
|||||||
session_id: string | null
|
session_id: string | null
|
||||||
started_at: string
|
started_at: string
|
||||||
status_code: number | null
|
status_code: number | null
|
||||||
|
timezone: string | null
|
||||||
trace_id: string | null
|
trace_id: string | null
|
||||||
user_agent: string | null
|
user_agent: string | null
|
||||||
user_id: string | null
|
user_id: string | null
|
||||||
@@ -2823,6 +2825,7 @@ export type Database = {
|
|||||||
ip_address_hash?: string | null
|
ip_address_hash?: string | null
|
||||||
method: string
|
method: string
|
||||||
parent_request_id?: string | null
|
parent_request_id?: string | null
|
||||||
|
referrer?: string | null
|
||||||
request_id: string
|
request_id: string
|
||||||
request_method?: string | null
|
request_method?: string | null
|
||||||
request_path?: string | null
|
request_path?: string | null
|
||||||
@@ -2832,6 +2835,7 @@ export type Database = {
|
|||||||
session_id?: string | null
|
session_id?: string | null
|
||||||
started_at?: string
|
started_at?: string
|
||||||
status_code?: number | null
|
status_code?: number | null
|
||||||
|
timezone?: string | null
|
||||||
trace_id?: string | null
|
trace_id?: string | null
|
||||||
user_agent?: string | null
|
user_agent?: string | null
|
||||||
user_id?: string | null
|
user_id?: string | null
|
||||||
@@ -2849,6 +2853,7 @@ export type Database = {
|
|||||||
ip_address_hash?: string | null
|
ip_address_hash?: string | null
|
||||||
method?: string
|
method?: string
|
||||||
parent_request_id?: string | null
|
parent_request_id?: string | null
|
||||||
|
referrer?: string | null
|
||||||
request_id?: string
|
request_id?: string
|
||||||
request_method?: string | null
|
request_method?: string | null
|
||||||
request_path?: string | null
|
request_path?: string | null
|
||||||
@@ -2858,6 +2863,7 @@ export type Database = {
|
|||||||
session_id?: string | null
|
session_id?: string | null
|
||||||
started_at?: string
|
started_at?: string
|
||||||
status_code?: number | null
|
status_code?: number | null
|
||||||
|
timezone?: string | null
|
||||||
trace_id?: string | null
|
trace_id?: string | null
|
||||||
user_agent?: string | null
|
user_agent?: string | null
|
||||||
user_id?: string | null
|
user_id?: string | null
|
||||||
@@ -5578,6 +5584,28 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Returns: undefined
|
Returns: undefined
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
Args: {
|
||||||
|
p_breadcrumbs?: string
|
||||||
|
p_client_version?: string
|
||||||
|
p_duration_ms?: number
|
||||||
|
p_endpoint?: string
|
||||||
|
p_environment_context?: string
|
||||||
|
p_error_message?: string
|
||||||
|
p_error_stack?: string
|
||||||
|
p_error_type?: string
|
||||||
|
p_method?: string
|
||||||
|
p_parent_request_id?: string
|
||||||
|
p_referrer?: string
|
||||||
|
p_request_id: string
|
||||||
|
p_status_code?: number
|
||||||
|
p_timezone?: string
|
||||||
|
p_trace_id?: string
|
||||||
|
p_user_agent?: string
|
||||||
|
p_user_id?: string
|
||||||
|
}
|
||||||
|
Returns: undefined
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
Args: {
|
Args: {
|
||||||
p_client_version?: string
|
p_client_version?: string
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ export async function trackRequest<T>(
|
|||||||
}
|
}
|
||||||
: { type: 'UnknownError', message: String(error), stack: undefined };
|
: { type: 'UnknownError', message: String(error), stack: undefined };
|
||||||
|
|
||||||
// Capture breadcrumbs only (environment stored as direct columns)
|
// Capture environment context and breadcrumbs
|
||||||
|
const envContext = captureEnvironmentContext();
|
||||||
const breadcrumbs = breadcrumbManager.getAll();
|
const breadcrumbs = breadcrumbManager.getAll();
|
||||||
|
|
||||||
// Log error to database (fire and forget)
|
// Log error to database (fire and forget)
|
||||||
@@ -92,6 +93,8 @@ export async function trackRequest<T>(
|
|||||||
clientVersion: context.clientVersion,
|
clientVersion: context.clientVersion,
|
||||||
parentRequestId: options.parentRequestId,
|
parentRequestId: options.parentRequestId,
|
||||||
traceId: context.traceId,
|
traceId: context.traceId,
|
||||||
|
timezone: envContext.timezone,
|
||||||
|
referrer: typeof document !== 'undefined' ? document.referrer : undefined,
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
logger.error('Failed to log error metadata', { error: err, context: 'RequestTracking' });
|
logger.error('Failed to log error metadata', { error: err, context: 'RequestTracking' });
|
||||||
});
|
});
|
||||||
@@ -118,6 +121,8 @@ interface RequestMetadata {
|
|||||||
clientVersion?: string;
|
clientVersion?: string;
|
||||||
parentRequestId?: string;
|
parentRequestId?: string;
|
||||||
traceId?: string;
|
traceId?: string;
|
||||||
|
timezone?: string;
|
||||||
|
referrer?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logRequestMetadata(metadata: RequestMetadata): Promise<void> {
|
async function logRequestMetadata(metadata: RequestMetadata): Promise<void> {
|
||||||
@@ -133,11 +138,13 @@ async function logRequestMetadata(metadata: RequestMetadata): Promise<void> {
|
|||||||
p_error_message: metadata.errorMessage ?? undefined,
|
p_error_message: metadata.errorMessage ?? undefined,
|
||||||
p_error_stack: metadata.errorStack ?? undefined,
|
p_error_stack: metadata.errorStack ?? undefined,
|
||||||
p_breadcrumbs: metadata.breadcrumbs ? JSON.stringify(metadata.breadcrumbs) : '[]',
|
p_breadcrumbs: metadata.breadcrumbs ? JSON.stringify(metadata.breadcrumbs) : '[]',
|
||||||
p_environment_context: '{}', // No longer used - environment stored as direct columns
|
p_environment_context: '{}', // Legacy parameter - no longer used
|
||||||
p_user_agent: metadata.userAgent ?? undefined,
|
p_user_agent: metadata.userAgent ?? undefined,
|
||||||
p_client_version: metadata.clientVersion ?? undefined,
|
p_client_version: metadata.clientVersion ?? undefined,
|
||||||
p_parent_request_id: metadata.parentRequestId ?? undefined,
|
p_parent_request_id: metadata.parentRequestId ?? undefined,
|
||||||
p_trace_id: metadata.traceId ?? undefined,
|
p_trace_id: metadata.traceId ?? undefined,
|
||||||
|
p_timezone: metadata.timezone ?? undefined,
|
||||||
|
p_referrer: metadata.referrer ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
-- Add missing environment context columns to request_metadata
|
||||||
|
ALTER TABLE public.request_metadata
|
||||||
|
ADD COLUMN IF NOT EXISTS timezone TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS referrer TEXT;
|
||||||
|
|
||||||
|
-- Add index for breadcrumbs lookup
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_request_breadcrumbs_request_id
|
||||||
|
ON public.request_breadcrumbs(request_id);
|
||||||
|
|
||||||
|
-- Update log_request_metadata function to accept and store new columns
|
||||||
|
CREATE OR REPLACE FUNCTION public.log_request_metadata(
|
||||||
|
p_request_id uuid,
|
||||||
|
p_user_id uuid DEFAULT NULL,
|
||||||
|
p_endpoint text DEFAULT NULL,
|
||||||
|
p_method text DEFAULT NULL,
|
||||||
|
p_status_code integer DEFAULT NULL,
|
||||||
|
p_duration_ms integer DEFAULT NULL,
|
||||||
|
p_error_type text DEFAULT NULL,
|
||||||
|
p_error_message text DEFAULT NULL,
|
||||||
|
p_user_agent text DEFAULT NULL,
|
||||||
|
p_client_version text DEFAULT NULL,
|
||||||
|
p_parent_request_id uuid DEFAULT NULL,
|
||||||
|
p_trace_id uuid DEFAULT NULL,
|
||||||
|
p_error_stack text DEFAULT NULL,
|
||||||
|
p_breadcrumbs text DEFAULT '[]',
|
||||||
|
p_environment_context text DEFAULT '{}',
|
||||||
|
p_timezone text DEFAULT NULL,
|
||||||
|
p_referrer text DEFAULT NULL
|
||||||
|
)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path TO 'public'
|
||||||
|
AS $function$
|
||||||
|
DECLARE
|
||||||
|
v_breadcrumb jsonb;
|
||||||
|
v_idx integer := 0;
|
||||||
|
BEGIN
|
||||||
|
-- Insert main metadata record
|
||||||
|
INSERT INTO request_metadata (
|
||||||
|
request_id, user_id, endpoint, method, status_code, duration_ms,
|
||||||
|
error_type, error_message, error_stack,
|
||||||
|
user_agent, client_version, parent_request_id, trace_id,
|
||||||
|
timezone, referrer
|
||||||
|
) VALUES (
|
||||||
|
p_request_id, p_user_id, p_endpoint, p_method, p_status_code, p_duration_ms,
|
||||||
|
p_error_type, p_error_message, p_error_stack,
|
||||||
|
p_user_agent, p_client_version, p_parent_request_id, p_trace_id,
|
||||||
|
p_timezone, p_referrer
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Parse and insert breadcrumbs into relational table
|
||||||
|
IF p_breadcrumbs IS NOT NULL AND p_breadcrumbs != '[]' THEN
|
||||||
|
BEGIN
|
||||||
|
FOR v_breadcrumb IN SELECT * FROM jsonb_array_elements(p_breadcrumbs::jsonb)
|
||||||
|
LOOP
|
||||||
|
INSERT INTO request_breadcrumbs (
|
||||||
|
request_id, timestamp, category, message, level, sequence_order
|
||||||
|
) VALUES (
|
||||||
|
p_request_id,
|
||||||
|
COALESCE((v_breadcrumb->>'timestamp')::timestamptz, NOW()),
|
||||||
|
COALESCE(v_breadcrumb->>'category', 'unknown'),
|
||||||
|
COALESCE(v_breadcrumb->>'message', ''),
|
||||||
|
COALESCE(v_breadcrumb->>'level', 'info')::text,
|
||||||
|
v_idx
|
||||||
|
);
|
||||||
|
v_idx := v_idx + 1;
|
||||||
|
END LOOP;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RAISE NOTICE 'Failed to parse breadcrumbs: %', SQLERRM;
|
||||||
|
END;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$function$;
|
||||||
Reference in New Issue
Block a user