mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
feat: Add ESLint rule for console statements
This commit is contained in:
280
docs/LOGGING_POLICY.md
Normal file
280
docs/LOGGING_POLICY.md
Normal file
@@ -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<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`:
|
||||||
|
|
||||||
|
```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
|
||||||
@@ -25,6 +25,8 @@ export default tseslint.config(
|
|||||||
rules: {
|
rules: {
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
"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-unused-vars": "warn",
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "warn",
|
"@typescript-eslint/no-unsafe-assignment": "warn",
|
||||||
@@ -49,6 +51,8 @@ export default tseslint.config(
|
|||||||
globals: globals.node,
|
globals: globals.node,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
// Console statement prevention (P0 #2 - Security Critical)
|
||||||
|
"no-console": ["error", { allow: ["warn", "error"] }],
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", {
|
"@typescript-eslint/explicit-function-return-type": ["error", {
|
||||||
|
|||||||
Reference in New Issue
Block a user