# Unified Logging Plan - Professional & Developer Friendly ## Problem Summary **Current Issues:** - Website logs are overly aggressive and verbose - Network errors show full stack traces (looks like syntax errors) - Multiple error formats for same issue - Not machine-readable - Different patterns than apps/api **Goal:** Create unified, professional logging that's both machine-readable AND beautiful for developers. ## Solution Overview ### 1. Unified Logger Interface (No Core Imports) ```typescript // apps/website/lib/interfaces/Logger.ts export interface Logger { debug(message: string, context?: unknown): void; info(message: string, context?: unknown): void; warn(message: string, context?: unknown): void; error(message: string, error?: Error, context?: unknown): void; } ``` ### 2. How Website Logging Aligns with apps/api **apps/api ConsoleLogger (Simple & Clean):** ```typescript // adapters/logging/ConsoleLogger.ts formatMessage(level: string, message: string, context?: unknown): string { const timestamp = new Date().toISOString(); const contextStr = context ? ` | ${JSON.stringify(context)}` : ''; return `[${timestamp}] ${level.toUpperCase()}: ${message}${contextStr}`; } // Output: [2026-01-06T12:00:00.000Z] WARN: Network error, retrying... | {"endpoint":"/auth/session"} ``` **apps/website ConsoleLogger (Enhanced & Developer-Friendly):** ```typescript // apps/website/lib/infrastructure/logging/ConsoleLogger.ts formatOutput(level: string, source: string, message: string, context?: unknown, error?: Error): void { const color = this.COLORS[level]; const emoji = this.EMOJIS[level]; const prefix = this.PREFIXES[level]; // Same core format as apps/api, but enhanced with colors/emojis console.groupCollapsed(`%c${emoji} [${source.toUpperCase()}] ${prefix}: ${message}`, `color: ${color}; font-weight: bold;`); console.log(`%cTimestamp:`, 'color: #666; font-weight: bold;', new Date().toISOString()); console.log(`%cContext:`, 'color: #666; font-weight: bold;'); console.dir(context, { depth: 3, colors: true }); console.groupEnd(); } // Output: ⚠️ [API] NETWORK WARN: Network error, retrying... // ├─ Timestamp: 2026-01-06T12:00:00.000Z // ├─ Context: { endpoint: "/auth/session", ... } ``` **Alignment:** - ✅ Same timestamp format (ISO 8601) - ✅ Same log levels (debug, info, warn, error) - ✅ Same context structure - ✅ Same message patterns - ✅ Website adds colors/emojis for better UX ### 3. Unified API Client Logging Strategy **Both apps/api and apps/website use the same patterns:** ```typescript // In BaseApiClient (shared logic): private handleError(error: ApiError): void { const severity = error.getSeverity(); const message = error.getDeveloperMessage(); // Same logic for both: if (error.context.isRetryable && error.context.retryCount > 0) { // Network errors during retry = warn (not error) this.logger.warn(message, error.context); } else if (severity === 'error') { // Final failure = error this.logger.error(message, error, error.context); } else { // Other errors = warn this.logger.warn(message, error.context); } } ``` ### 4. Unified Error Classification **Both environments use the same severity levels:** - **error**: Critical failures (server down, auth failures, data corruption) - **warn**: Expected errors (network timeouts, CORS, validation failures) - **info**: Normal operations (successful retries, connectivity恢复) - **debug**: Detailed info (development only) ### 5. Example: Same Error, Different Output **Scenario: Server down, retrying connection** **apps/api output:** ``` [2026-01-06T12:00:00.000Z] WARN: [NETWORK_ERROR] GET /auth/session retry:1 | {"endpoint":"/auth/session","method":"GET","retryCount":1} ``` **apps/website output:** ``` ⚠️ [API] NETWORK WARN: GET /auth/session - retry:1 ├─ Timestamp: 2026-01-06T12:00:00.000Z ├─ Endpoint: /auth/session ├─ Method: GET ├─ Retry Count: 1 └─ Hint: Check if API server is running and CORS is configured ``` **Key Alignment Points:** 1. **Same log level**: `warn` (not `error`) 2. **Same context**: `{endpoint, method, retryCount}` 3. **Same message pattern**: Includes retry count 4. **Same timestamp format**: ISO 8601 5. **Website just adds**: Colors, emojis, and developer hints This creates a **unified logging ecosystem** where: - Logs can be parsed the same way - Severity levels mean the same thing - Context structures are identical - Website enhances for developer experience - apps/api keeps it simple for server logs ## Implementation Files ### File 1: Logger Interface **Path:** `apps/website/lib/interfaces/Logger.ts` ```typescript export interface Logger { debug(message: string, context?: unknown): void; info(message: string, context?: unknown): void; warn(message: string, context?: unknown): void; error(message: string, error?: Error, context?: unknown): void; } ``` ### File 2: ErrorReporter Interface **Path:** `apps/website/lib/interfaces/ErrorReporter.ts` ```typescript export interface ErrorReporter { report(error: Error, context?: unknown): void; } ``` ### File 3: Enhanced ConsoleLogger (Human-Readable Only) **Path:** `apps/website/lib/infrastructure/logging/ConsoleLogger.ts` ```typescript import { Logger } from '../../interfaces/Logger'; export class ConsoleLogger implements Logger { private readonly COLORS = { debug: '#888888', info: '#00aaff', warn: '#ffaa00', error: '#ff4444' }; private readonly EMOJIS = { debug: '🐛', info: 'ℹ️', warn: '⚠️', error: '❌' }; private readonly PREFIXES = { debug: 'DEBUG', info: 'INFO', warn: 'WARN', error: 'ERROR' }; private shouldLog(level: string): boolean { if (process.env.NODE_ENV === 'test') return level === 'error'; if (process.env.NODE_ENV === 'production') return level !== 'debug'; return true; } private formatOutput(level: string, source: string, message: string, context?: unknown, error?: Error): void { const color = this.COLORS[level]; const emoji = this.EMOJIS[level]; const prefix = this.PREFIXES[level]; console.groupCollapsed(`%c${emoji} [${source.toUpperCase()}] ${prefix}: ${message}`, `color: ${color}; font-weight: bold;`); console.log(`%cTimestamp:`, 'color: #666; font-weight: bold;', new Date().toISOString()); console.log(`%cSource:`, 'color: #666; font-weight: bold;', source); if (context) { console.log(`%cContext:`, 'color: #666; font-weight: bold;'); console.dir(context, { depth: 3, colors: true }); } if (error) { console.log(`%cError Details:`, 'color: #666; font-weight: bold;'); console.log(`%cType:`, 'color: #ff4444; font-weight: bold;', error.name); console.log(`%cMessage:`, 'color: #ff4444; font-weight: bold;', error.message); if (process.env.NODE_ENV === 'development' && error.stack) { console.log(`%cStack Trace:`, 'color: #666; font-weight: bold;'); console.log(error.stack); } } console.groupEnd(); } debug(message: string, context?: unknown): void { if (!this.shouldLog('debug')) return; this.formatOutput('debug', 'website', message, context); } info(message: string, context?: unknown): void { if (!this.shouldLog('info')) return; this.formatOutput('info', 'website', message, context); } warn(message: string, context?: unknown): void { if (!this.shouldLog('warn')) return; this.formatOutput('warn', 'website', message, context); } error(message: string, error?: Error, context?: unknown): void { if (!this.shouldLog('error')) return; this.formatOutput('error', 'website', message, context, error); } } ``` ### File 4: ConsoleErrorReporter **Path:** `apps/website/lib/infrastructure/ConsoleErrorReporter.ts` ```typescript import { ErrorReporter } from '../../interfaces/ErrorReporter'; export class ConsoleErrorReporter implements ErrorReporter { report(error: Error, context?: unknown): void { const timestamp = new Date().toISOString(); console.error(`[${timestamp}] Error reported:`, error.message, { error, context }); } } ``` ### File 5: Updated BaseApiClient **Path:** `apps/website/lib/api/base/BaseApiClient.ts` **Key Changes:** 1. **Update createNetworkError:** ```typescript private createNetworkError(error: Error, method: string, path: string, retryCount: number = 0): ApiError { // ... existing logic ... return new ApiError( message, errorType, { endpoint: path, method, timestamp: new Date().toISOString(), retryCount, troubleshooting: this.getTroubleshootingContext(error, path), isRetryable: retryableTypes.includes(errorType), isConnectivity: errorType === 'NETWORK_ERROR' || errorType === 'TIMEOUT_ERROR', developerHint: this.getDeveloperHint(error, path, method), }, error ); } private getDeveloperHint(error: Error, path: string, method: string): string { if (error.message.includes('fetch failed') || error.message.includes('Failed to fetch')) { return 'Check if API server is running and CORS is configured correctly'; } if (error.message.includes('timeout')) { return 'Request timed out - consider increasing timeout or checking network'; } if (error.message.includes('ECONNREFUSED')) { return 'Connection refused - verify API server address and port'; } return 'Review network connection and API endpoint configuration'; } ``` 2. **Update handleError:** ```typescript private handleError(error: ApiError): void { const severity = error.getSeverity(); const message = error.getDeveloperMessage(); const enhancedContext = { ...error.context, severity, isRetryable: error.isRetryable(), isConnectivity: error.isConnectivityIssue(), }; if (severity === 'error') { this.logger.error(message, error, enhancedContext); } else if (severity === 'warn') { this.logger.warn(message, enhancedContext); } else { this.logger.info(message, enhancedContext); } this.errorReporter.report(error, enhancedContext); } ``` 3. **Update request method logging:** ```typescript // In catch block: } catch (error) { const responseTime = Date.now() - startTime; if (error instanceof ApiError) { // Reduce verbosity - only log final failure if (process.env.NODE_ENV === 'development' && requestId) { try { const apiLogger = getGlobalApiLogger(); // This will use warn level for retryable errors apiLogger.logError(requestId, error, responseTime); } catch (e) { // Silent fail } } throw error; } // ... rest of error handling } ``` ### File 6: Updated ApiRequestLogger **Path:** `apps/website/lib/infrastructure/ApiRequestLogger.ts` **Key Changes:** ```typescript // Update logError to use warn for network errors: logError(id: string, error: Error, duration: number): void { // ... existing setup ... const isNetworkError = error.message.includes('fetch') || error.message.includes('Failed to fetch') || error.message.includes('NetworkError'); if (this.options.logToConsole) { const emoji = isNetworkError ? '⚠️' : '❌'; const prefix = isNetworkError ? 'NETWORK WARN' : 'ERROR'; const color = isNetworkError ? '#ffaa00' : '#ff4444'; console.groupCollapsed( `%c${emoji} [API] ${prefix}: ${log.method} ${log.url}`, `color: ${color}; font-weight: bold; font-size: 12px;` ); console.log(`%cRequest ID:`, 'color: #666; font-weight: bold;', id); console.log(`%cDuration:`, 'color: #666; font-weight: bold;', `${duration.toFixed(2)}ms`); console.log(`%cError:`, 'color: #666; font-weight: bold;', error.message); console.log(`%cType:`, 'color: #666; font-weight: bold;', error.name); if (process.env.NODE_ENV === 'development' && error.stack) { console.log(`%cStack:`, 'color: #666; font-weight: bold;'); console.log(error.stack); } console.groupEnd(); } // Don't report network errors to external services if (!isNetworkError) { const globalHandler = getGlobalErrorHandler(); globalHandler.report(error, { source: 'api_request', url: log.url, method: log.method, duration, requestId: id, }); } } ``` ### File 7: Updated GlobalErrorHandler **Path:** `apps/website/lib/infrastructure/GlobalErrorHandler.ts` **Key Changes:** ```typescript // In handleWindowError: private handleWindowError = (event: ErrorEvent): void => { const error = event.error; // Check if this is a network/CORS error (expected in some cases) if (error instanceof TypeError && error.message.includes('fetch')) { this.logger.warn('Network error detected', { type: 'network_error', message: error.message, url: event.filename }); return; // Don't prevent default for network errors } // ... existing logic for other errors ... }; // Update logErrorWithMaximumDetail: private logErrorWithMaximumDetail(error: Error | ApiError, context: Record): void { if (!this.options.verboseLogging) return; const isApiError = error instanceof ApiError; const isWarning = isApiError && error.getSeverity() === 'warn'; if (isWarning) { console.groupCollapsed(`%c⚠️ [WARNING] ${error.message}`, 'color: #ffaa00; font-weight: bold; font-size: 14px;'); console.log('Context:', context); console.groupEnd(); return; } // Full error details for actual errors console.groupCollapsed(`%c❌ [ERROR] ${error.name || 'Error'}: ${error.message}`, 'color: #ff4444; font-weight: bold; font-size: 16px;'); console.log('%cError Details:', 'color: #ff4444; font-weight: bold; font-size: 14px;'); console.table({ Name: error.name, Message: error.message, Type: isApiError ? error.type : 'N/A', Severity: isApiError ? error.getSeverity() : 'error', Retryable: isApiError ? error.isRetryable() : 'N/A', }); console.log('%cContext:', 'color: #666; font-weight: bold; font-size: 14px;'); console.dir(context, { depth: 4, colors: true }); if (process.env.NODE_ENV === 'development' && error.stack) { console.log('%cStack Trace:', 'color: #666; font-weight: bold; font-size: 14px;'); console.log(error.stack); } if (isApiError && error.context?.developerHint) { console.log('%c💡 Developer Hint:', 'color: #00aaff; font-weight: bold; font-size: 14px;'); console.log(error.context.developerHint); } console.groupEnd(); this.logger.error(error.message, error, context); } ``` ## Expected Results ### Before (Current - Too Verbose) ``` [API-ERROR] [NETWORK_ERROR] Unable to connect to server. Possible CORS or network issue. GET /auth/session { error: Error [ApiError]: Unable to connect to server. Possible CORS or network issue. at AuthApiClient.createNetworkError (lib/api/base/BaseApiClient.ts:136:12) at executeRequest (lib/api/base/BaseApiClient.ts:314:31) ... } [USER-NOTIFICATION] Unable to connect to the server. Please check your internet connection. [API ERROR] GET http://api:3000/auth/session Request ID: req_1767694969495_4 Duration: 8.00ms Error: Unable to connect to server. Possible CORS or network issue. Type: ApiError Stack: ApiError: Unable to connect to server... ``` ### After (Unified - Clean & Beautiful) Beautiful console output: ``` ⚠️ [API] NETWORK WARN: GET /auth/session - retry:1 ├─ Request ID: req_123 ├─ Duration: 8.00ms ├─ Error: fetch failed ├─ Type: TypeError └─ Hint: Check if API server is running and CORS is configured ``` And user notification (separate): ``` [USER-NOTIFICATION] Unable to connect to the server. Please check your internet connection. ``` ## Benefits ✅ **Developer-Friendly**: Beautiful colors, emojis, and formatting ✅ **Reduced Noise**: Appropriate severity levels prevent spam ✅ **Unified Format**: Same patterns as apps/api ✅ **No Core Imports**: Website remains independent ✅ **Professional**: Industry-standard logging practices ✅ **Clean Output**: Human-readable only, no JSON clutter ## Testing Checklist 1. **Server Down**: Should show warn level, no stack trace, retry attempts 2. **CORS Error**: Should show warn level with troubleshooting hint 3. **Auth Error (401)**: Should show warn level, retryable 4. **Server Error (500)**: Should show error level with full details 5. **Validation Error (400)**: Should show warn level, not retryable 6. **Successful Call**: Should show info level with duration ## Quick Implementation Run these commands to implement: ```bash # 1. Create interfaces mkdir -p apps/website/lib/interfaces cat > apps/website/lib/interfaces/Logger.ts << 'EOF' export interface Logger { debug(message: string, context?: unknown): void; info(message: string, context?: unknown): void; warn(message: string, context?: unknown): void; error(message: string, error?: Error, context?: unknown): void; } EOF # 2. Create ErrorReporter interface cat > apps/website/lib/interfaces/ErrorReporter.ts << 'EOF' export interface ErrorReporter { report(error: Error, context?: unknown): void; } EOF # 3. Update ConsoleLogger (use the enhanced version above) # 4. Update ConsoleErrorReporter (use the version above) # 5. Update BaseApiClient (use the changes above) # 6. Update ApiRequestLogger (use the changes above) # 7. Update GlobalErrorHandler (use the changes above) ``` This single plan provides everything needed to transform the logging from verbose and confusing to professional and beautiful! 🎨