11 KiB
Logging Policy
✅ Console Statement Prevention (P0 #2)
Status: Enforced via ESLint
Severity: Critical - Security & Information Leakage
The Problem
Console statements in production code cause:
- Information leakage: Sensitive data exposed in browser console
- Performance overhead: Console operations are expensive
- Unprofessional UX: Users see debug output
- No structured logging: Can't filter, search, or analyze logs effectively
128 console statements were found during the security audit.
The Solution
✅ Use handleError() for Application Errors
CRITICAL: All application errors MUST be logged to the Admin Panel Error Log (/admin/error-monitoring)
import { handleError } from '@/lib/errorHandler';
// ❌ DON'T use console or raw toast for errors
try {
await fetchData();
} catch (error) {
console.error('Failed:', error); // ❌ No admin logging
toast.error('Failed to load data'); // ❌ Not tracked
}
// ✅ DO use handleError() for application errors
try {
await fetchData();
} catch (error) {
handleError(error, {
action: 'Load Data',
userId: user?.id,
metadata: { entityId, context: 'DataLoader' }
});
throw error; // Re-throw for parent error boundaries
}
✅ Use the Structured Logger for Non-Error Logging
import { logger } from '@/lib/logger';
// ❌ DON'T use console
console.log('User logged in:', userId);
// ✅ DO use structured logger
logger.info('User logged in', { userId });
logger.debug('Auth state changed', { state, userId });
Error Handling Method
// Application errors (REQUIRED for errors that need admin visibility)
handleError(
error: unknown,
context: {
action: string; // What operation failed
userId?: string; // Who was affected
metadata?: Record<string, unknown>; // Additional context
}
): string // Returns error reference ID
What handleError() does:
- Logs error to
request_metadatatable (Admin Panel visibility) - Shows user-friendly toast with reference ID
- Captures breadcrumbs and environment context
- Makes errors searchable in
/admin/error-monitoring - Returns error reference ID for tracking
Logger Methods (for non-error logging)
// Information (development only)
logger.info(message: string, context?: Record<string, unknown>);
// Warnings (development + production)
logger.warn(message: string, context?: Record<string, unknown>);
// Errors (development + production, but prefer handleError() for app errors)
logger.error(message: string, context?: Record<string, unknown>);
// Debug (very verbose, development only)
logger.debug(message: string, context?: Record<string, unknown>);
Benefits of Structured Error Handling & Logging
- Admin visibility: All errors logged to Admin Panel (
/admin/error-monitoring) - User-friendly: Shows toast with reference ID for support tickets
- Context preservation: Rich metadata for debugging
- Searchable: Filter by user, action, date, error type
- Trackable: Each error gets unique reference ID
- Automatic filtering: Development logs show everything, production shows warnings/errors
- Security: Prevents accidental PII exposure
ESLint Enforcement
The no-console rule is enforced in eslint.config.js:
"no-console": "error" // Blocks ALL console statements
This rule will:
- ❌ Block:
console.log(),console.debug(),console.info(),console.warn(),console.error() - ✅ Use instead:
logger.*for logging,handleError()for error handling
Running Lint
# Check for violations
npm run lint
# Auto-fix where possible
npm run lint -- --fix
Migration Guide
1. Replace console.error in catch blocks with handleError()
// Before
try {
await saveData();
} catch (error) {
console.error('Save failed:', error);
toast.error('Failed to save');
}
// After
try {
await saveData();
} catch (error) {
handleError(error, {
action: 'Save Data',
userId: user?.id,
metadata: { entityId, entityType }
});
throw error; // Re-throw for parent components
}
2. Replace console.log with logger.info
// Before
console.log('[ModerationQueue] Fetching submissions');
// After
logger.info('Fetching submissions', { component: 'ModerationQueue' });
3. Replace console.debug with logger.debug
// Before
console.log('[DEBUG] Auth state:', authState);
// After
logger.debug('Auth state', { authState });
4. Replace console.warn with logger.warn
// Before
console.warn('localStorage error:', error);
// After
logger.warn('localStorage error', { error });
Examples
Good: Error Handling with Admin Logging
import { handleError } from '@/lib/errorHandler';
import { logger } from '@/lib/logger';
const handleSubmit = async () => {
logger.info('Starting submission', {
entityType,
entityId,
userId
});
try {
const result = await submitData();
logger.info('Submission successful', {
submissionId: result.id,
processingTime: Date.now() - startTime
});
toast.success('Submission created successfully');
} catch (error) {
// handleError logs to admin panel + shows toast
const errorId = handleError(error, {
action: 'Submit Data',
userId,
metadata: { entityType, entityId }
});
throw error; // Re-throw for parent error boundaries
}
};
Bad: Console Logging
const handleSubmit = async () => {
console.log('Submitting...'); // ❌ Will fail ESLint
try {
const result = await submitData();
console.log('Success:', result); // ❌ Will fail ESLint
} catch (error) {
console.error(error); // ❌ Will fail ESLint
toast.error('Failed'); // ❌ Not logged to admin panel
}
};
When to Use What
Use handleError() for:
- ✅ Database errors (fetch, insert, update, delete)
- ✅ API call failures
- ✅ Form submission errors
- ✅ Authentication errors
- ✅ Any error that users should report to support
- ✅ Any error that needs admin investigation
Use logger.* for:
- ✅ Debug information (development only)
- ✅ Performance tracking
- ✅ Component lifecycle events
- ✅ Non-error warnings (localStorage issues, etc.)
Use toast.* (without handleError) for:
- ✅ Success messages
- ✅ Info messages
- ✅ User-facing validation errors (no admin logging needed)
NEVER use console.*:
- ❌ All console statements are blocked by ESLint
- ❌ Use
handleError()orlogger.*instead
Environment-Aware Logging
The logger automatically adjusts based on environment:
// Development: All logs shown
logger.debug('Verbose details'); // ✅ Visible
logger.info('Operation started'); // ✅ Visible
logger.warn('Potential issue'); // ✅ Visible
logger.error('Critical error'); // ✅ Visible
// Production: Only warnings and errors
logger.debug('Verbose details'); // ❌ Hidden
logger.info('Operation started'); // ❌ Hidden
logger.warn('Potential issue'); // ✅ Visible
logger.error('Critical error'); // ✅ Visible + Sent to monitoring
Testing with Logger
import { logger } from '@/lib/logger';
// Mock logger in tests
jest.mock('@/lib/logger', () => ({
logger: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}
}));
test('logs error on failure', async () => {
await failingOperation();
expect(logger.error).toHaveBeenCalledWith(
'Operation failed',
expect.objectContaining({ error: expect.any(String) })
);
});
Monitoring Integration (Future)
The logger is designed to integrate with:
- Sentry: Automatic error tracking
- LogRocket: Session replay with logs
- Datadog: Log aggregation and analysis
- Custom dashboards: Structured JSON logs
// Future: Logs will automatically flow to monitoring services
logger.error('Payment failed', {
userId,
amount,
paymentProvider
});
// → Automatically sent to Sentry with full context
// → Triggers alert if error rate exceeds threshold
Edge Function Logging
Using edgeLogger in Edge Functions
Edge functions use the edgeLogger utility from _shared/logger.ts:
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
const handler = async (req: Request): Promise<Response> => {
const tracking = startRequest('function-name');
try {
edgeLogger.info('Processing request', {
requestId: tracking.requestId,
// ... context
});
// ... your code
const duration = endRequest(tracking);
edgeLogger.info('Request completed', { requestId: tracking.requestId, duration });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const duration = endRequest(tracking);
edgeLogger.error('Request failed', {
error: errorMessage,
requestId: tracking.requestId,
duration
});
}
};
Logger Methods for Edge Functions
edgeLogger.info()- General information loggingedgeLogger.warn()- Warning conditionsedgeLogger.error()- Error conditionsedgeLogger.debug()- Detailed debugging (dev only)
All logs are visible in the Supabase Edge Function Logs dashboard.
CRITICAL: Never use console.* in edge functions. Always use edgeLogger.* instead.
Summary
Use handleError() for application errors → Logs to Admin Panel + user-friendly toast
Use logger.* for general logging (client-side) → Environment-aware console output
Use edgeLogger.* for edge function logging → Structured logs visible in Supabase dashboard
Never use console.* → Blocked by ESLint
This approach ensures:
- ✅ Production builds are clean (no console noise)
- ✅ All errors are tracked and actionable in Admin Panel
- ✅ Users get helpful error messages with reference IDs
- ✅ Development remains productive with detailed logs
- ✅ Edge functions have structured, searchable logs
Admin Panel Error Monitoring
All errors logged via handleError() are visible in the Admin Panel at:
Path: /admin/error-monitoring
Features:
- Search and filter errors by action, user, date range
- View error context (metadata, breadcrumbs, environment)
- Track error frequency and patterns
- One-click copy of error details for debugging
Access: Admin role required
Updated: 2025-11-03
Status: ✅ Enforced via ESLint (Frontend + Edge Functions)
See Also:
src/lib/errorHandler.ts- Error handling utilitiessrc/lib/logger.ts- Logger implementationeslint.config.js- Enforcement configurationdocs/JSONB_ELIMINATION.md- Related improvements