From 3ee65403ea213e4fb81dc640bd06a20ebb4c4387 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:46:49 +0000 Subject: [PATCH] feat: Add ESLint rule for console statements --- docs/LOGGING_POLICY.md | 280 +++++++++++++++++++++++++++++++++++++++++ eslint.config.js | 4 + 2 files changed, 284 insertions(+) create mode 100644 docs/LOGGING_POLICY.md diff --git a/docs/LOGGING_POLICY.md b/docs/LOGGING_POLICY.md new file mode 100644 index 00000000..7fe1f6eb --- /dev/null +++ b/docs/LOGGING_POLICY.md @@ -0,0 +1,280 @@ +# 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 + +```typescript +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 + +```typescript +// Information (development only) +logger.info(message: string, context?: Record); + +// Warnings (development + production) +logger.warn(message: string, context?: Record); + +// Errors (always logged, sent to monitoring in production) +logger.error(message: string, context?: Record); + +// Debug (very verbose, development only) +logger.debug(message: string, context?: Record); +``` + +### 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`: + +```javascript +"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 + +```bash +# Check for violations +npm run lint + +# Auto-fix where possible +npm run lint -- --fix +``` + +--- + +## Migration Guide + +### 1. Replace Console.log with Logger.info + +```typescript +// Before +console.log('[ModerationQueue] Fetching submissions'); + +// After +logger.info('Fetching submissions', { component: 'ModerationQueue' }); +``` + +### 2. Replace Console.error with Logger.error + +```typescript +// 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 + +```typescript +// Before +console.log('[DEBUG] Auth state:', authState); + +// After +logger.debug('Auth state', { authState }); +``` + +### 4. Use Toast for User-Facing Messages + +```typescript +// 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 + +```typescript +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 + +```typescript +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: + +```typescript +// 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 + +```typescript +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 + +```typescript +// 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 diff --git a/eslint.config.js b/eslint.config.js index 2b4a8d02..6676e12f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -25,6 +25,8 @@ export default tseslint.config( rules: { ...reactHooks.configs.recommended.rules, "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + // Console statement prevention (P0 #2 - Security Critical) + "no-console": ["error", { allow: ["warn", "error"] }], "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unsafe-assignment": "warn", @@ -49,6 +51,8 @@ export default tseslint.config( globals: globals.node, }, rules: { + // Console statement prevention (P0 #2 - Security Critical) + "no-console": ["error", { allow: ["warn", "error"] }], "@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/explicit-function-return-type": ["error", {