logger.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /**
  2. * Production-safe logging utility for GrabZilla
  3. *
  4. * Logging levels:
  5. * - ERROR: Critical errors that prevent functionality
  6. * - WARN: Non-critical issues that may affect user experience
  7. * - INFO: Important state changes and operations
  8. * - DEBUG: Detailed debugging information (disabled in production)
  9. *
  10. * Security considerations:
  11. * - Never logs full file paths (only filenames)
  12. * - Sanitizes URLs (removes query params)
  13. * - Redacts sensitive data (cookie files, API keys)
  14. * - DEBUG level completely disabled in production
  15. */
  16. const path = require('path');
  17. // Determine if running in production
  18. const isProduction = process.env.NODE_ENV === 'production' || !process.env.NODE_ENV;
  19. // Log levels
  20. const LogLevel = {
  21. ERROR: 0,
  22. WARN: 1,
  23. INFO: 2,
  24. DEBUG: 3
  25. };
  26. // Current log level (DEBUG disabled in production)
  27. const currentLevel = isProduction ? LogLevel.INFO : LogLevel.DEBUG;
  28. /**
  29. * Sanitize file path to show only filename
  30. */
  31. function sanitizeFilePath(filePath) {
  32. if (!filePath || typeof filePath !== 'string') return '[invalid-path]';
  33. try {
  34. return path.basename(filePath);
  35. } catch {
  36. return '[path-error]';
  37. }
  38. }
  39. /**
  40. * Sanitize URL to remove query parameters
  41. */
  42. function sanitizeUrl(url) {
  43. if (!url || typeof url !== 'string') return '[invalid-url]';
  44. try {
  45. const parsed = new URL(url);
  46. return `${parsed.protocol}//${parsed.hostname}${parsed.pathname}`;
  47. } catch {
  48. // Not a valid URL, might be a file path or other string
  49. return '[sanitized]';
  50. }
  51. }
  52. /**
  53. * Sanitize object by removing/redacting sensitive fields
  54. */
  55. function sanitizeObject(obj) {
  56. if (!obj || typeof obj !== 'object') return obj;
  57. const sanitized = Array.isArray(obj) ? [] : {};
  58. for (const [key, value] of Object.entries(obj)) {
  59. const lowerKey = key.toLowerCase();
  60. // Redact sensitive fields
  61. if (lowerKey.includes('cookie') || lowerKey.includes('auth') || lowerKey.includes('token') || lowerKey.includes('key')) {
  62. sanitized[key] = '[REDACTED]';
  63. }
  64. // Sanitize file paths
  65. else if (lowerKey.includes('path') && typeof value === 'string') {
  66. sanitized[key] = sanitizeFilePath(value);
  67. }
  68. // Sanitize URLs
  69. else if (lowerKey.includes('url') && typeof value === 'string') {
  70. sanitized[key] = sanitizeUrl(value);
  71. }
  72. // Recursively sanitize nested objects
  73. else if (value && typeof value === 'object') {
  74. sanitized[key] = sanitizeObject(value);
  75. }
  76. else {
  77. sanitized[key] = value;
  78. }
  79. }
  80. return sanitized;
  81. }
  82. /**
  83. * Format log message with timestamp and level
  84. */
  85. function formatMessage(level, ...args) {
  86. const timestamp = new Date().toISOString();
  87. const levelStr = ['ERROR', 'WARN', 'INFO', 'DEBUG'][level];
  88. const prefix = `[${timestamp}] [${levelStr}]`;
  89. // Sanitize arguments
  90. const sanitizedArgs = args.map(arg => {
  91. if (typeof arg === 'string') {
  92. // Check if string looks like a URL
  93. if (arg.startsWith('http://') || arg.startsWith('https://')) {
  94. return sanitizeUrl(arg);
  95. }
  96. // Check if string looks like a file path
  97. if (arg.includes('/') || arg.includes('\\')) {
  98. return sanitizeFilePath(arg);
  99. }
  100. return arg;
  101. }
  102. if (typeof arg === 'object') {
  103. return sanitizeObject(arg);
  104. }
  105. return arg;
  106. });
  107. return [prefix, ...sanitizedArgs];
  108. }
  109. /**
  110. * Log error message (always shown)
  111. */
  112. function error(...args) {
  113. if (currentLevel >= LogLevel.ERROR) {
  114. console.error(...formatMessage(LogLevel.ERROR, ...args));
  115. }
  116. }
  117. /**
  118. * Log warning message (shown in production and development)
  119. */
  120. function warn(...args) {
  121. if (currentLevel >= LogLevel.WARN) {
  122. console.warn(...formatMessage(LogLevel.WARN, ...args));
  123. }
  124. }
  125. /**
  126. * Log info message (shown in production and development)
  127. */
  128. function info(...args) {
  129. if (currentLevel >= LogLevel.INFO) {
  130. console.log(...formatMessage(LogLevel.INFO, ...args));
  131. }
  132. }
  133. /**
  134. * Log debug message (development only)
  135. */
  136. function debug(...args) {
  137. if (currentLevel >= LogLevel.DEBUG) {
  138. console.log(...formatMessage(LogLevel.DEBUG, ...args));
  139. }
  140. }
  141. /**
  142. * Legacy console.log replacement - maps to debug level
  143. * Use specific levels (error/warn/info/debug) instead
  144. */
  145. function log(...args) {
  146. debug('[LEGACY]', ...args);
  147. }
  148. module.exports = {
  149. error,
  150. warn,
  151. info,
  152. debug,
  153. log,
  154. // Utility functions for manual sanitization if needed
  155. sanitizeFilePath,
  156. sanitizeUrl,
  157. sanitizeObject,
  158. // Expose for testing
  159. isProduction,
  160. currentLevel,
  161. LogLevel
  162. };