UNPKG

magically-sdk

Version:

Official SDK for Magically - Build mobile apps with AI

304 lines (253 loc) â€ĸ 8.84 kB
/** * Simple, formatted logger for Magically SDK * Provides clean, readable debug output */ export class Logger { private isDebugEnabled: boolean; private prefix: string; private requestCounter = 0; constructor(isDebugEnabled: boolean = false, prefix: string = 'MagicallySDK') { this.isDebugEnabled = isDebugEnabled; this.prefix = prefix; } /** * Log debug information (only when debug is enabled) */ debug(step: string, data?: any): void { if (!this.isDebugEnabled) return; const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; console.log(`🔧 [${this.prefix}] ${timestamp} - ${step}`); if (data) { console.log(' 📄 Data:', this.sanitizeData(data)); } } /** * Log success steps */ success(step: string, data?: any): void { if (!this.isDebugEnabled) return; const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; console.log(`✅ [${this.prefix}] ${timestamp} - ${step}`); if (data) { console.log(' 📄 Data:', this.sanitizeData(data)); } } /** * Log errors (always shown, regardless of debug flag) */ error(step: string, error?: any): void { const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; console.error(`❌ [${this.prefix}] ${timestamp} - ${step}`); console.error(' 🚨 Error:', error); } /** * Log warnings */ warn(step: string, data?: any): void { if (!this.isDebugEnabled) return; const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; console.warn(`âš ī¸ [${this.prefix}] ${timestamp} - ${step}`); if (data) { console.warn(' 📄 Data:', this.sanitizeData(data)); } } /** * Log info (always shown) */ info(step: string, data?: any): void { const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; console.log(`â„šī¸ [${this.prefix}] ${timestamp} - ${step}`); if (data) { console.log(' 📄 Data:', this.sanitizeData(data)); } } /** * Sanitize sensitive data for logging */ private sanitizeData(data: any): any { if (typeof data !== 'object' || data === null) { return data; } const sanitized = { ...data }; // Remove sensitive fields const sensitiveFields = [ 'password', 'token', 'accessToken', 'refreshToken', 'access_token', 'refresh_token', 'client_secret', 'authorization', 'auth', 'secret' ]; sensitiveFields.forEach(field => { if (sanitized[field]) { sanitized[field] = '[REDACTED]'; } }); // Truncate long URLs but keep important parts if (sanitized.url && sanitized.url.length > 100) { const url = new URL(sanitized.url); sanitized.url = `${url.origin}${url.pathname}...`; } return sanitized; } /** * Create a scoped logger for a specific component */ scope(scopeName: string): Logger { return new Logger(this.isDebugEnabled, `${this.prefix}:${scopeName}`); } /** * Generate unique request ID */ private generateRequestId(): string { return `req-${++this.requestCounter}-${Date.now()}`; } /** * Sanitize URLs for logging */ private sanitizeUrl(url: string): string { try { const urlObj = new URL(url); // Keep the important parts but remove query params that might contain sensitive data return `${urlObj.origin}${urlObj.pathname}`; } catch { return url.substring(0, 100) + '...'; } } /** * Sanitize headers for logging */ private sanitizeHeaders(headers: Record<string, string>): Record<string, string> { const sanitized = { ...headers }; const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'set-cookie']; Object.keys(sanitized).forEach(key => { if (sensitiveHeaders.includes(key.toLowerCase())) { sanitized[key] = '[REDACTED]'; } }); return sanitized; } /** * Log network request */ networkRequest(method: string, url: string, options?: { headers?: Record<string, string>; body?: any; requestId?: string; operation?: string; }): string { const requestId = options?.requestId || this.generateRequestId(); const startTime = Date.now(); if (!this.isDebugEnabled) return requestId; const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; const operation = options?.operation ? `:${options.operation}` : ''; // Send BOTH formats for compatibility // 1. Original string format (for backward compatibility) let message = `[Network] ${this.prefix}${operation} ${timestamp} - Request [${requestId}]\n`; message += ` ${method} ${this.sanitizeUrl(url)}`; if (options?.headers) { message += `\n Headers: ${JSON.stringify(this.sanitizeHeaders(options.headers))}`; } if (options?.body) { message += `\n Body: ${JSON.stringify(this.sanitizeData(options.body))}`; } console.log(message); // 2. Also send structured data with __raw prefix const structuredData = { __raw: true, type: 'network', data: { requestId, method: method as any, url, startTime, operation: options?.operation, requestHeaders: options?.headers ? this.sanitizeHeaders(options.headers) : undefined, requestBody: options?.body ? this.sanitizeData(options.body) : undefined, } }; console.log(JSON.stringify(structuredData)); return requestId; } /** * Log network response */ networkResponse(requestId: string, response: { status: number; statusText?: string; duration?: number; data?: any; headers?: Record<string, string>; operation?: string; }): void { if (!this.isDebugEnabled) return; const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; const durationText = response.duration ? ` (${response.duration}ms)` : ''; const operation = response.operation ? `:${response.operation}` : ''; const endTime = Date.now(); // 1. Original string format let message = `[Network] ${this.prefix}${operation} ${timestamp} - Response [${requestId}]${durationText}\n`; message += ` Status: ${response.status} ${response.statusText || ''}`; if (response.headers) { message += `\n Headers: ${JSON.stringify(this.sanitizeHeaders(response.headers))}`; } if (response.data !== undefined) { message += `\n Data: ${JSON.stringify(this.sanitizeData(response.data))}`; } // Use appropriate console method based on status if (response.status >= 400) { console.error(message); } else { console.log(message); } // 2. Also send structured data with __raw prefix const structuredData = { __raw: true, type: 'network_response', data: { requestId, status: response.status, statusText: response.statusText, endTime, duration: response.duration, operation: response.operation, responseHeaders: response.headers ? this.sanitizeHeaders(response.headers) : undefined, responseBody: response.data !== undefined ? this.sanitizeData(response.data) : undefined, responseSize: response.data ? JSON.stringify(response.data).length : 0, } }; // Use appropriate console method based on status if (response.status >= 400) { console.error(JSON.stringify(structuredData)); } else { console.log(JSON.stringify(structuredData)); } } /** * Log network error */ networkError(requestId: string, error: any, options?: { duration?: number; operation?: string; }): void { const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; const durationText = options?.duration ? ` (${options.duration}ms)` : ''; const operation = options?.operation ? `:${options.operation}` : ''; const endTime = Date.now(); const errorMessage = error?.message || error?.toString() || 'Unknown error'; // 1. Original string format const message = `[Network] ${this.prefix}${operation} ${timestamp} - Error [${requestId}]${durationText}\n Error: ${errorMessage}`; console.error(message); // 2. Also send structured data with __raw prefix const structuredData = { __raw: true, type: 'network_error', data: { requestId, error: error?.responseData || error, // Pass the full error/response data errorStack: error?.stack, endTime, duration: options?.duration, operation: options?.operation, } }; console.error(JSON.stringify(structuredData)); } }