Files
thrilltrack-explorer/docs/LOGGING_POLICY.md
2025-11-03 18:05:58 +00:00

9.2 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:

  1. Logs error to request_metadata table (Admin Panel visibility)
  2. Shows user-friendly toast with reference ID
  3. Captures breadcrumbs and environment context
  4. Makes errors searchable in /admin/error-monitoring
  5. 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

  1. Admin visibility: All errors logged to Admin Panel (/admin/error-monitoring)
  2. User-friendly: Shows toast with reference ID for support tickets
  3. Context preservation: Rich metadata for debugging
  4. Searchable: Filter by user, action, date, error type
  5. Trackable: Each error gets unique reference ID
  6. Automatic filtering: Development logs show everything, production shows warnings/errors
  7. 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() or logger.* 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

Summary

Use handleError() for all application errors - Logs to Admin Panel
Use logger.* for non-error logging - Structured and filterable
Provide rich context with every log - Makes debugging easier
Use appropriate log levels (debug/info/warn/error) - Environment-aware
Let ESLint catch violations early - No console statements allowed
Never log sensitive data (passwords, tokens, PII) - Security critical
Re-throw errors after handleError() - Let parent error boundaries catch them


Admin Panel Error Monitoring

All errors logged via handleError() are visible in the Admin Panel:

  • URL: /admin/error-monitoring
  • Features: Search by user, action, date, error type
  • Reference IDs: Each error has a unique ID shown to users
  • Context: Full metadata and breadcrumbs for debugging

See Also:

  • src/lib/errorHandler.ts - Error handling utilities
  • src/lib/logger.ts - Logger implementation
  • eslint.config.js - Enforcement configuration
  • docs/JSONB_ELIMINATION.md - Related improvements