logging
This commit is contained in:
@@ -25,6 +25,9 @@ export interface ApiErrorContext {
|
|||||||
troubleshooting?: string;
|
troubleshooting?: string;
|
||||||
source?: string;
|
source?: string;
|
||||||
componentStack?: string;
|
componentStack?: string;
|
||||||
|
isRetryable?: boolean;
|
||||||
|
isConnectivity?: boolean;
|
||||||
|
developerHint?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
|
|||||||
@@ -143,11 +143,43 @@ export class BaseApiClient {
|
|||||||
retryCount,
|
retryCount,
|
||||||
// Add helpful context for developers
|
// Add helpful context for developers
|
||||||
troubleshooting: this.getTroubleshootingContext(error, path),
|
troubleshooting: this.getTroubleshootingContext(error, path),
|
||||||
|
isRetryable: this.isRetryableError(errorType),
|
||||||
|
isConnectivity: errorType === 'NETWORK_ERROR' || errorType === 'TIMEOUT_ERROR',
|
||||||
|
developerHint: this.getDeveloperHint(error, path, method),
|
||||||
},
|
},
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if error type is retryable
|
||||||
|
*/
|
||||||
|
private isRetryableError(errorType: ApiErrorType): boolean {
|
||||||
|
const retryableTypes: ApiErrorType[] = [
|
||||||
|
'NETWORK_ERROR',
|
||||||
|
'SERVER_ERROR',
|
||||||
|
'RATE_LIMIT_ERROR',
|
||||||
|
'TIMEOUT_ERROR',
|
||||||
|
];
|
||||||
|
return retryableTypes.includes(errorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get developer-friendly hint for troubleshooting
|
||||||
|
*/
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get troubleshooting context for network errors
|
* Get troubleshooting context for network errors
|
||||||
*/
|
*/
|
||||||
@@ -359,17 +391,25 @@ export class BaseApiClient {
|
|||||||
const severity = error.getSeverity();
|
const severity = error.getSeverity();
|
||||||
const message = error.getDeveloperMessage();
|
const message = error.getDeveloperMessage();
|
||||||
|
|
||||||
// Log based on severity
|
// Enhanced context for better debugging
|
||||||
|
const enhancedContext = {
|
||||||
|
...error.context,
|
||||||
|
severity,
|
||||||
|
isRetryable: error.isRetryable(),
|
||||||
|
isConnectivity: error.isConnectivityIssue(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use appropriate log level
|
||||||
if (severity === 'error') {
|
if (severity === 'error') {
|
||||||
this.logger.error(message, error, error.context);
|
this.logger.error(message, error, enhancedContext);
|
||||||
} else if (severity === 'warn') {
|
} else if (severity === 'warn') {
|
||||||
this.logger.warn(message, error.context);
|
this.logger.warn(message, enhancedContext);
|
||||||
} else {
|
} else {
|
||||||
this.logger.info(message, error.context);
|
this.logger.info(message, enhancedContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report to error tracking
|
// Report to error tracking
|
||||||
this.errorReporter.report(error, error.context);
|
this.errorReporter.report(error, enhancedContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get<T>(path: string, options?: BaseApiClientOptions): Promise<T> {
|
protected get<T>(path: string, options?: BaseApiClientOptions): Promise<T> {
|
||||||
|
|||||||
@@ -177,12 +177,13 @@ export class ApiRequestLogger {
|
|||||||
this.activeRequests.set(id, log);
|
this.activeRequests.set(id, log);
|
||||||
|
|
||||||
if (this.options.logToConsole) {
|
if (this.options.logToConsole) {
|
||||||
console.groupCollapsed(`%c[API REQUEST] ${method} ${url}`, 'color: #00aaff; font-weight: bold;');
|
// Use enhanced logger for beautiful output
|
||||||
console.log('Request ID:', id);
|
this.logger.debug(`API Request: ${method} ${url}`, {
|
||||||
console.log('Timestamp:', timestamp);
|
requestId: id,
|
||||||
if (headers) console.log('Headers:', log.headers);
|
timestamp,
|
||||||
if (body && this.options.logBodies) console.log('Body:', log.body);
|
headers: this.options.logBodies ? log.headers : '[headers hidden]',
|
||||||
console.groupEnd();
|
body: this.options.logBodies ? log.body : '[body hidden]',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
@@ -209,15 +210,19 @@ export class ApiRequestLogger {
|
|||||||
this.addToHistory(log);
|
this.addToHistory(log);
|
||||||
|
|
||||||
if (this.options.logToConsole) {
|
if (this.options.logToConsole) {
|
||||||
const statusColor = response.ok ? '#00ff88' : '#ff4444';
|
const isSuccess = response.ok;
|
||||||
console.groupCollapsed(`%c[API RESPONSE] ${log.method} ${log.url} - ${response.status}`, `color: ${statusColor}; font-weight: bold;`);
|
const context = {
|
||||||
console.log('Request ID:', id);
|
requestId: id,
|
||||||
console.log('Duration:', `${duration.toFixed(2)}ms`);
|
duration: `${duration.toFixed(2)}ms`,
|
||||||
console.log('Status:', `${response.status} ${response.statusText}`);
|
status: `${response.status} ${response.statusText}`,
|
||||||
if (this.options.logResponses) {
|
...(this.options.logResponses && { body: log.response.body }),
|
||||||
console.log('Response Body:', log.response.body);
|
};
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
this.logger.info(`API Response: ${log.method} ${log.url}`, context);
|
||||||
|
} else {
|
||||||
|
this.logger.warn(`API Response: ${log.method} ${log.url}`, context);
|
||||||
}
|
}
|
||||||
console.groupEnd();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,26 +245,40 @@ export class ApiRequestLogger {
|
|||||||
this.addToHistory(log);
|
this.addToHistory(log);
|
||||||
|
|
||||||
if (this.options.logToConsole) {
|
if (this.options.logToConsole) {
|
||||||
console.groupCollapsed(`%c[API ERROR] ${log.method} ${log.url}`, 'color: #ff4444; font-weight: bold;');
|
const isNetworkError = error.message.includes('fetch') ||
|
||||||
console.log('Request ID:', id);
|
error.message.includes('Failed to fetch') ||
|
||||||
console.log('Duration:', `${duration.toFixed(2)}ms`);
|
error.message.includes('NetworkError');
|
||||||
console.log('Error:', error.message);
|
|
||||||
console.log('Type:', error.name);
|
const context = {
|
||||||
if (error.stack) {
|
requestId: id,
|
||||||
console.log('Stack:', error.stack);
|
duration: `${duration.toFixed(2)}ms`,
|
||||||
|
errorType: error.name,
|
||||||
|
...(process.env.NODE_ENV === 'development' && error.stack ? { stack: error.stack } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use warn level for network errors (expected), error level for others
|
||||||
|
if (isNetworkError) {
|
||||||
|
this.logger.warn(`API Network Error: ${log.method} ${log.url}`, context);
|
||||||
|
} else {
|
||||||
|
this.logger.error(`API Error: ${log.method} ${log.url}`, error, context);
|
||||||
}
|
}
|
||||||
console.groupEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report to global error handler
|
// Don't report network errors to external services (they're expected)
|
||||||
const globalHandler = getGlobalErrorHandler();
|
const isNetworkError = error.message.includes('fetch') ||
|
||||||
globalHandler.report(error, {
|
error.message.includes('Failed to fetch') ||
|
||||||
source: 'api_request',
|
error.message.includes('NetworkError');
|
||||||
url: log.url,
|
|
||||||
method: log.method,
|
if (!isNetworkError) {
|
||||||
duration,
|
const globalHandler = getGlobalErrorHandler();
|
||||||
requestId: id,
|
globalHandler.report(error, {
|
||||||
});
|
source: 'api_request',
|
||||||
|
url: log.url,
|
||||||
|
method: log.method,
|
||||||
|
duration,
|
||||||
|
requestId: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
8
apps/website/lib/infrastructure/ConsoleErrorReporter.ts
Normal file
8
apps/website/lib/infrastructure/ConsoleErrorReporter.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -112,6 +112,18 @@ export class GlobalErrorHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
line: event.lineno,
|
||||||
|
col: event.colno,
|
||||||
|
});
|
||||||
|
return; // Don't prevent default for network errors
|
||||||
|
}
|
||||||
|
|
||||||
const enhancedContext = this.captureEnhancedContext('window_error', {
|
const enhancedContext = this.captureEnhancedContext('window_error', {
|
||||||
filename: event.filename,
|
filename: event.filename,
|
||||||
lineno: event.lineno,
|
lineno: event.lineno,
|
||||||
@@ -119,14 +131,13 @@ export class GlobalErrorHandler {
|
|||||||
message: event.message,
|
message: event.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log with maximum detail
|
// Log with appropriate detail
|
||||||
this.logErrorWithMaximumDetail(error, enhancedContext);
|
this.logErrorWithMaximumDetail(error, enhancedContext);
|
||||||
|
|
||||||
// Store in history
|
// Store in history
|
||||||
this.addToHistory(error, enhancedContext);
|
this.addToHistory(error, enhancedContext);
|
||||||
|
|
||||||
|
// Report to external if enabled (but not for network errors)
|
||||||
// Report to external if enabled
|
|
||||||
if (this.options.reportToExternal) {
|
if (this.options.reportToExternal) {
|
||||||
this.reportToExternal(error, enhancedContext);
|
this.reportToExternal(error, enhancedContext);
|
||||||
}
|
}
|
||||||
@@ -154,19 +165,27 @@ export class GlobalErrorHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a network error (expected)
|
||||||
|
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||||
|
this.logger.warn('Unhandled promise rejection - network error', {
|
||||||
|
type: 'network_error',
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const enhancedContext = this.captureEnhancedContext('unhandled_promise', {
|
const enhancedContext = this.captureEnhancedContext('unhandled_promise', {
|
||||||
promise: event.promise,
|
promise: event.promise,
|
||||||
reason: typeof error === 'string' ? error : error?.message || 'Unknown promise rejection',
|
reason: typeof error === 'string' ? error : error?.message || 'Unknown promise rejection',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log with maximum detail
|
// Log with appropriate detail
|
||||||
this.logErrorWithMaximumDetail(error, enhancedContext);
|
this.logErrorWithMaximumDetail(error, enhancedContext);
|
||||||
|
|
||||||
// Store in history
|
// Store in history
|
||||||
this.addToHistory(error, enhancedContext);
|
this.addToHistory(error, enhancedContext);
|
||||||
|
|
||||||
|
// Report to external if enabled (but not for network errors)
|
||||||
// Report to external if enabled
|
|
||||||
if (this.options.reportToExternal) {
|
if (this.options.reportToExternal) {
|
||||||
this.reportToExternal(error, enhancedContext);
|
this.reportToExternal(error, enhancedContext);
|
||||||
}
|
}
|
||||||
@@ -284,74 +303,44 @@ export class GlobalErrorHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log error with maximum detail
|
* Log error with appropriate detail
|
||||||
*/
|
*/
|
||||||
private logErrorWithMaximumDetail(error: Error | ApiError, context: Record<string, unknown>): void {
|
private logErrorWithMaximumDetail(error: Error | ApiError, context: Record<string, unknown>): void {
|
||||||
if (!this.options.verboseLogging) return;
|
if (!this.options.verboseLogging) return;
|
||||||
|
|
||||||
const isApiError = error instanceof ApiError;
|
const isApiError = error instanceof ApiError;
|
||||||
|
const isWarning = isApiError && error.getSeverity() === 'warn';
|
||||||
// Group all related information
|
|
||||||
console.groupCollapsed(`%c[GLOBAL ERROR] ${error.name || 'Error'}: ${error.message}`,
|
|
||||||
'color: #ff4444; font-weight: bold; font-size: 14px;'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Error details
|
if (isWarning) {
|
||||||
console.log('Error Details:', {
|
// Simplified warning output
|
||||||
name: error.name,
|
this.logger.warn(error.message, context);
|
||||||
message: error.message,
|
return;
|
||||||
stack: error.stack,
|
|
||||||
type: isApiError ? error.type : 'N/A',
|
|
||||||
severity: isApiError ? error.getSeverity() : 'error',
|
|
||||||
retryable: isApiError ? error.isRetryable() : 'N/A',
|
|
||||||
connectivity: isApiError ? error.isConnectivityIssue() : 'N/A',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Context information
|
|
||||||
console.log('Context:', context);
|
|
||||||
|
|
||||||
// Enhanced stack trace
|
|
||||||
if (context.enhancedStack) {
|
|
||||||
console.log('Enhanced Stack Trace:\n' + context.enhancedStack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API-specific information
|
// Full error details for actual errors
|
||||||
if (isApiError && error.context) {
|
|
||||||
console.log('API Context:', error.context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment information
|
|
||||||
console.log('Environment:', {
|
|
||||||
mode: process.env.NODE_ENV,
|
|
||||||
nextPublicMode: process.env.NEXT_PUBLIC_GRIDPILOT_MODE,
|
|
||||||
version: process.env.NEXT_PUBLIC_APP_VERSION,
|
|
||||||
buildTime: process.env.NEXT_PUBLIC_BUILD_TIME,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Performance metrics
|
|
||||||
if (context.memory && typeof context.memory === 'object' &&
|
|
||||||
'usedJSHeapSize' in context.memory && 'totalJSHeapSize' in context.memory) {
|
|
||||||
const memory = context.memory as { usedJSHeapSize: number; totalJSHeapSize: number };
|
|
||||||
console.log('Memory Usage:', {
|
|
||||||
used: `${(memory.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
|
|
||||||
total: `${(memory.totalJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Network information
|
|
||||||
if (context.connection) {
|
|
||||||
console.log('Network:', context.connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error history (last 5 errors)
|
|
||||||
if (this.errorHistory.length > 0) {
|
|
||||||
console.log('Recent Error History:', this.errorHistory.slice(-5));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.groupEnd();
|
|
||||||
|
|
||||||
// Also log to our logger
|
|
||||||
this.logger.error(error.message, error, context);
|
this.logger.error(error.message, error, context);
|
||||||
|
|
||||||
|
// In development, show additional details
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.groupCollapsed(`%c[ERROR DETAIL] ${error.name || 'Error'}`, 'color: #ff4444; font-weight: bold; font-size: 14px;');
|
||||||
|
|
||||||
|
console.log('%cError Details:', 'color: #666; font-weight: bold;', {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
type: isApiError ? error.type : 'N/A',
|
||||||
|
severity: isApiError ? error.getSeverity() : 'error',
|
||||||
|
retryable: isApiError ? error.isRetryable() : 'N/A',
|
||||||
|
connectivity: isApiError ? error.isConnectivityIssue() : 'N/A',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('%cContext:', 'color: #666; font-weight: bold;', context);
|
||||||
|
|
||||||
|
if (isApiError && error.context?.developerHint) {
|
||||||
|
console.log('%c💡 Developer Hint:', 'color: #00aaff; font-weight: bold;', error.context.developerHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -444,7 +433,17 @@ export class GlobalErrorHandler {
|
|||||||
report(error: Error | ApiError, additionalContext: Record<string, unknown> = {}): void {
|
report(error: Error | ApiError, additionalContext: Record<string, unknown> = {}): void {
|
||||||
const context = this.captureEnhancedContext('manual_report', additionalContext);
|
const context = this.captureEnhancedContext('manual_report', additionalContext);
|
||||||
|
|
||||||
this.logErrorWithMaximumDetail(error, context);
|
// Check if this is a network error (don't report to external services)
|
||||||
|
const isNetworkError = error.message.includes('fetch') ||
|
||||||
|
error.message.includes('Failed to fetch') ||
|
||||||
|
error.message.includes('NetworkError');
|
||||||
|
|
||||||
|
if (isNetworkError) {
|
||||||
|
this.logger.warn(`Manual error report: ${error.message}`, context);
|
||||||
|
} else {
|
||||||
|
this.logErrorWithMaximumDetail(error, context);
|
||||||
|
}
|
||||||
|
|
||||||
this.addToHistory(error, context);
|
this.addToHistory(error, context);
|
||||||
|
|
||||||
// Auto-capture for replay in development
|
// Auto-capture for replay in development
|
||||||
@@ -453,7 +452,8 @@ export class GlobalErrorHandler {
|
|||||||
replaySystem.autoCapture(error, context);
|
replaySystem.autoCapture(error, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.reportToExternal) {
|
// Only report non-network errors to external services
|
||||||
|
if (this.options.reportToExternal && !isNetworkError) {
|
||||||
this.reportToExternal(error, context);
|
this.reportToExternal(error, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,81 @@
|
|||||||
import { Logger } from '../../interfaces/Logger';
|
import { Logger } from '../../interfaces/Logger';
|
||||||
|
|
||||||
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
||||||
|
|
||||||
export class ConsoleLogger implements Logger {
|
export class ConsoleLogger implements Logger {
|
||||||
private formatMessage(level: string, message: string, context?: unknown): string {
|
private readonly COLORS: Record<LogLevel, string> = {
|
||||||
const timestamp = new Date().toISOString();
|
debug: '#888888',
|
||||||
const contextStr = context ? ` | ${JSON.stringify(context)}` : '';
|
info: '#00aaff',
|
||||||
return `[${timestamp}] ${level.toUpperCase()}: ${message}${contextStr}`;
|
warn: '#ffaa00',
|
||||||
|
error: '#ff4444',
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly EMOJIS: Record<LogLevel, string> = {
|
||||||
|
debug: '🐛',
|
||||||
|
info: 'ℹ️',
|
||||||
|
warn: '⚠️',
|
||||||
|
error: '❌',
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly PREFIXES: Record<LogLevel, string> = {
|
||||||
|
debug: 'DEBUG',
|
||||||
|
info: 'INFO',
|
||||||
|
warn: 'WARN',
|
||||||
|
error: 'ERROR',
|
||||||
|
};
|
||||||
|
|
||||||
|
private shouldLog(level: LogLevel): boolean {
|
||||||
|
if (process.env.NODE_ENV === 'test') return level === 'error';
|
||||||
|
if (process.env.NODE_ENV === 'production') return level !== 'debug';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatOutput(level: LogLevel, 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 {
|
debug(message: string, context?: unknown): void {
|
||||||
// Always log debug in development and test environments
|
if (!this.shouldLog('debug')) return;
|
||||||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
|
this.formatOutput('debug', 'website', message, context);
|
||||||
console.debug(this.formatMessage('debug', message, context));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info(message: string, context?: unknown): void {
|
info(message: string, context?: unknown): void {
|
||||||
// Always log info - we need transparency in all environments
|
if (!this.shouldLog('info')) return;
|
||||||
console.info(this.formatMessage('info', message, context));
|
this.formatOutput('info', 'website', message, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
warn(message: string, context?: unknown): void {
|
warn(message: string, context?: unknown): void {
|
||||||
console.warn(this.formatMessage('warn', message, context));
|
if (!this.shouldLog('warn')) return;
|
||||||
|
this.formatOutput('warn', 'website', message, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: string, error?: Error, context?: unknown): void {
|
error(message: string, error?: Error, context?: unknown): void {
|
||||||
const errorStr = error ? ` | Error: ${error.message}` : '';
|
if (!this.shouldLog('error')) return;
|
||||||
console.error(this.formatMessage('error', message, context) + errorStr);
|
this.formatOutput('error', 'website', message, context, error);
|
||||||
|
|
||||||
// In development, also show enhanced error info
|
|
||||||
if (process.env.NODE_ENV === 'development' && error) {
|
|
||||||
console.groupCollapsed(`%c[ERROR DETAIL] ${message}`, 'color: #ff4444; font-weight: bold;');
|
|
||||||
console.log('Error Object:', error);
|
|
||||||
console.log('Stack Trace:', error.stack);
|
|
||||||
console.log('Context:', context);
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
526
plans/UNIFIED_LOGGING_PLAN.md
Normal file
526
plans/UNIFIED_LOGGING_PLAN.md
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
# 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<string, unknown>): 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! 🎨
|
||||||
Reference in New Issue
Block a user