Files
thrilltrack-explorer/docs/LOGGING_POLICY.md
2025-11-03 14:46:49 +00:00

6.3 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 the Structured Logger

import { logger } from '@/lib/logger';

// ❌ DON'T use console
console.log('User logged in:', userId);
console.error('Failed to load data:', error);

// ✅ DO use structured logger
logger.info('User logged in', { userId });
logger.error('Failed to load data', { error, context: 'DataLoader' });

Logger Methods

// Information (development only)
logger.info(message: string, context?: Record<string, unknown>);

// Warnings (development + production)
logger.warn(message: string, context?: Record<string, unknown>);

// Errors (always logged, sent to monitoring in production)
logger.error(message: string, context?: Record<string, unknown>);

// Debug (very verbose, development only)
logger.debug(message: string, context?: Record<string, unknown>);

Benefits of Structured Logging

  1. Automatic filtering: Production logs only show errors/warnings
  2. Context preservation: Rich metadata for debugging
  3. Searchable: Can filter by userId, action, context, etc.
  4. Integration ready: Works with Sentry, LogRocket, etc.
  5. Security: Prevents accidental PII exposure

ESLint Enforcement

The no-console rule is enforced in eslint.config.js:

"no-console": ["error", { allow: ["warn", "error"] }]

This rule will:

  • Block: console.log(), console.debug(), console.info()
  • Allow: console.warn(), console.error() (for critical edge cases only)

Running Lint

# Check for violations
npm run lint

# Auto-fix where possible
npm run lint -- --fix

Migration Guide

1. Replace Console.log with Logger.info

// Before
console.log('[ModerationQueue] Fetching submissions');

// After
logger.info('Fetching submissions', { component: 'ModerationQueue' });

2. Replace Console.error with Logger.error

// Before
console.error('Upload failed:', error);

// After
logger.error('Upload failed', { 
  error: error instanceof Error ? error.message : String(error),
  stack: error instanceof Error ? error.stack : undefined
});

3. Replace Debug Logs with Logger.debug

// Before
console.log('[DEBUG] Auth state:', authState);

// After
logger.debug('Auth state', { authState });

4. Use Toast for User-Facing Messages

// Before
console.error('Failed to save changes');

// After
logger.error('Failed to save changes', { userId, entityId });
toast.error('Failed to save changes', {
  description: 'Please try again or contact support.'
});

Examples

Good: Structured Logging with Context

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 
    });
  } catch (error) {
    logger.error('Submission failed', { 
      error: getErrorMessage(error),
      entityType,
      entityId,
      userId 
    });
    
    toast.error('Submission failed', {
      description: 'Please try again.'
    });
  }
};

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); // ⚠️ Allowed but discouraged
  }
};

When Console.warn/error is Acceptable

Only in these rare cases:

  1. Third-party library issues: Debugging external library behavior
  2. Build/bundler errors: Issues during development build process
  3. Critical failures: Logger itself has failed (extremely rare)

In 99% of cases, use the structured 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

Always use logger.* instead of console.*
Provide rich context with every log
Use appropriate log levels (debug/info/warn/error)
Let ESLint catch violations early
Never log sensitive data (passwords, tokens, PII)


See Also:

  • src/lib/logger.ts - Logger implementation
  • eslint.config.js - Enforcement configuration
  • docs/PHASE_1_JSONB_COMPLETE.md - Related improvements