UNPKG

a11yanalyze

Version:

A command-line tool for developers and QA engineers to test web pages and websites for WCAG 2.2 AA accessibility compliance

540 lines 18.3 kB
"use strict"; /** * Error Logging and Technical Issue Reporting System * Provides comprehensive error tracking, logging, and technical diagnostics */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorLogger = void 0; const fs_1 = require("fs"); const path_1 = require("path"); /** * Comprehensive error logging and technical issue reporting system */ class ErrorLogger { constructor(config = {}) { this.errors = []; this.technicalIssues = new Map(); this.config = { enableFileLogging: true, logFilePath: './logs/a11yanalyze.log', minLevel: 'info', maxFileSize: 10 * 1024 * 1024, // 10MB maxFiles: 5, enableConsoleLogging: true, includeStackTraces: true, structuredLogging: true, enableRotation: true, ...config, }; this.sessionId = this.generateSessionId(); this.startTime = new Date(); this.initializeLogging(); } /** * Log an error entry */ logError(level, category, message, details) { const errorId = this.generateErrorId(); const timestamp = new Date().toISOString(); const errorEntry = { id: errorId, level, category, message, details: details?.error?.message || details?.context?.details, stack: this.config.includeStackTraces ? details?.error?.stack : undefined, url: details?.url, timestamp, source: details?.source || 'unknown', context: details?.context, recoveryAction: details?.recoveryAction, recovered: details?.recovered || false, }; // Check if this error meets minimum logging level if (this.shouldLogLevel(level)) { this.errors.push(errorEntry); // Write to file if enabled if (this.config.enableFileLogging) { this.writeToFile(errorEntry); } // Log to console if enabled if (this.config.enableConsoleLogging) { this.logToConsole(errorEntry); } // Check for technical issues this.analyzeForTechnicalIssues(errorEntry); } return errorId; } /** * Log debug information */ debug(message, source, context) { return this.logError('debug', 'system', message, { source, context }); } /** * Log informational message */ info(message, source, context) { return this.logError('info', 'system', message, { source, context }); } /** * Log warning */ warn(message, category = 'unknown', source, context) { return this.logError('warn', category, message, { source, context }); } /** * Log error */ error(message, category = 'unknown', error, source, context) { return this.logError('error', category, message, { error, source, context }); } /** * Log fatal error */ fatal(message, category = 'unknown', error, source, context) { return this.logError('fatal', category, message, { error, source, context }); } /** * Log browser-specific errors */ logBrowserError(message, error, url, recoveryAction) { return this.logError('error', 'browser', message, { error, url, source: 'browser-manager', recoveryAction, recovered: !!recoveryAction, }); } /** * Log network errors */ logNetworkError(message, url, error, responseCode) { return this.logError('error', 'network', message, { error, url, source: 'network', context: { responseCode }, }); } /** * Log scanning errors */ logScanningError(message, url, error, scanPhase) { return this.logError('error', 'scanning', message, { error, url, source: 'page-scanner', context: { scanPhase }, }); } /** * Log crawling errors */ logCrawlingError(message, url, error, depth) { return this.logError('error', 'crawling', message, { error, url, source: 'site-crawler', context: { depth }, }); } /** * Log timeout errors with recovery information */ logTimeoutError(message, url, timeout, recoveryAction) { return this.logError('warn', 'timeout', message, { url, source: 'timeout-handler', context: { timeout }, recoveryAction, recovered: !!recoveryAction, }); } /** * Report a technical issue */ reportTechnicalIssue(type, severity, title, description, affectedUrls, technicalDetails, suggestedFixes = []) { const issueId = this.generateIssueId(); const timestamp = new Date().toISOString(); const existingIssue = Array.from(this.technicalIssues.values()) .find(issue => issue.title === title && issue.type === type); if (existingIssue) { // Update existing issue existingIssue.lastOccurrence = timestamp; existingIssue.occurrenceCount += 1; existingIssue.affectedUrls = [...new Set([...existingIssue.affectedUrls, ...affectedUrls])]; return existingIssue.id; } const technicalIssue = { id: issueId, type, severity, title, description, affectedUrls, suggestedFixes, technicalDetails, firstOccurrence: timestamp, lastOccurrence: timestamp, occurrenceCount: 1, }; this.technicalIssues.set(issueId, technicalIssue); // Log the technical issue as an error this.logError('error', 'system', `Technical issue reported: ${title}`, { source: 'technical-issue-reporter', context: { issueId, type, severity }, }); return issueId; } /** * Collect system diagnostics */ async collectSystemDiagnostics() { const os = await Promise.resolve().then(() => __importStar(require('os'))); const process = await Promise.resolve().then(() => __importStar(require('process'))); return { system: { platform: os.platform(), arch: os.arch(), nodeVersion: process.version, memory: { total: os.totalmem(), used: os.totalmem() - os.freemem(), percentage: ((os.totalmem() - os.freemem()) / os.totalmem()) * 100, }, cpu: { model: os.cpus()[0]?.model || 'unknown', cores: os.cpus().length, }, }, browser: { name: 'chromium', version: 'unknown', // Would be populated by browser manager userAgent: 'a11yanalyze/1.0.0', }, network: { connectivity: 'online', // Would be tested dynamically dnsResolution: true, // Would be tested dynamically }, configuration: { scanOptions: {}, crawlOptions: {}, outputOptions: {}, }, }; } /** * Generate error statistics */ getErrorStatistics() { const byLevel = { debug: 0, info: 0, warn: 0, error: 0, fatal: 0 }; const byCategory = { browser: 0, network: 0, parsing: 0, timeout: 0, validation: 0, configuration: 0, scanning: 0, crawling: 0, output: 0, system: 0, unknown: 0 }; // Count errors by level and category this.errors.forEach(error => { byLevel[error.level]++; byCategory[error.category]++; }); // Generate timeline (simplified) const timeline = this.errors.map(error => ({ timestamp: error.timestamp, level: error.level, category: error.category, count: 1, })); // Find frequent errors const errorMessages = new Map(); this.errors.forEach(error => { const key = error.message; const existing = errorMessages.get(key); if (existing) { existing.count++; } else { errorMessages.set(key, { count: 1, level: error.level, category: error.category }); } }); const frequentErrors = Array.from(errorMessages.entries()) .map(([message, data]) => ({ message, ...data })) .sort((a, b) => b.count - a.count) .slice(0, 10); // Calculate recovery statistics const recoveryAttempts = this.errors.filter(e => e.recoveryAction).length; const successfulRecoveries = this.errors.filter(e => e.recovered).length; return { byLevel, byCategory, timeline, frequentErrors, recovery: { totalAttempts: recoveryAttempts, successfulRecoveries, recoveryRate: recoveryAttempts > 0 ? successfulRecoveries / recoveryAttempts : 0, }, }; } /** * Get all errors for reporting */ getErrors(level, category) { return this.errors.filter(error => { if (level && error.level !== level) return false; if (category && error.category !== category) return false; return true; }); } /** * Get all technical issues */ getTechnicalIssues() { return Array.from(this.technicalIssues.values()); } /** * Export error log to file */ exportErrorLog(filePath, format = 'json') { const data = { sessionId: this.sessionId, startTime: this.startTime.toISOString(), endTime: new Date().toISOString(), errors: this.errors, technicalIssues: Array.from(this.technicalIssues.values()), statistics: this.getErrorStatistics(), }; switch (format) { case 'json': (0, fs_1.writeFileSync)(filePath, JSON.stringify(data, null, 2)); break; case 'csv': this.exportToCsv(filePath); break; case 'txt': this.exportToText(filePath); break; } } /** * Clear all logged errors */ clear() { this.errors = []; this.technicalIssues.clear(); this.sessionId = this.generateSessionId(); this.startTime = new Date(); } /** * Get session information */ getSessionInfo() { return { id: this.sessionId, startTime: this.startTime, errorCount: this.errors.length, issueCount: this.technicalIssues.size, }; } /** * Private helper methods */ initializeLogging() { if (this.config.enableFileLogging && this.config.logFilePath) { const logDir = (0, path_1.dirname)(this.config.logFilePath); if (!(0, fs_1.existsSync)(logDir)) { (0, fs_1.mkdirSync)(logDir, { recursive: true }); } } // Log session start this.info('Error logging session started', 'error-logger', { sessionId: this.sessionId, config: this.config, }); } shouldLogLevel(level) { return ErrorLogger.LEVEL_PRIORITIES[level] >= ErrorLogger.LEVEL_PRIORITIES[this.config.minLevel]; } writeToFile(error) { if (!this.config.logFilePath) return; try { const logLine = this.config.structuredLogging ? JSON.stringify(error) + '\n' : this.formatTextLogLine(error) + '\n'; (0, fs_1.appendFileSync)(this.config.logFilePath, logLine); // Check for rotation if enabled if (this.config.enableRotation) { this.checkLogRotation(); } } catch (err) { // Avoid infinite recursion by not logging this error console.error('Failed to write to log file:', err); } } logToConsole(error) { const message = `[${error.level.toUpperCase()}] ${error.category}: ${error.message}`; switch (error.level) { case 'debug': console.debug(message); break; case 'info': console.info(message); break; case 'warn': console.warn(message); break; case 'error': case 'fatal': console.error(message); if (error.stack && this.config.includeStackTraces) { console.error(error.stack); } break; } } analyzeForTechnicalIssues(error) { // Analyze error patterns to automatically generate technical issues if (error.level === 'error' || error.level === 'fatal') { if (error.category === 'timeout') { this.reportTechnicalIssue('performance', 'medium', 'Timeout Issues Detected', `Multiple timeout errors detected during scanning: ${error.message}`, error.url ? [error.url] : [], { errorId: error.id, category: error.category }, ['Increase timeout values', 'Check network connectivity', 'Optimize page load performance']); } } } formatTextLogLine(error) { return `${error.timestamp} [${error.level.toUpperCase()}] ${error.category}:${error.source} - ${error.message}`; } checkLogRotation() { // Simplified rotation logic - in production, would use proper log rotation if (!this.config.logFilePath) return; try { const stats = require('fs').statSync(this.config.logFilePath); if (stats.size > this.config.maxFileSize) { // Rotate log file const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const rotatedPath = this.config.logFilePath + '.' + timestamp; require('fs').renameSync(this.config.logFilePath, rotatedPath); } } catch (err) { // Ignore rotation errors } } exportToCsv(filePath) { const headers = ['timestamp', 'level', 'category', 'source', 'message', 'url', 'recovered']; const rows = this.errors.map(error => [ error.timestamp, error.level, error.category, error.source, error.message.replace(/,/g, ';'), // Escape commas error.url || '', error.recovered.toString(), ]); const csvContent = [headers, ...rows].map(row => row.join(',')).join('\n'); (0, fs_1.writeFileSync)(filePath, csvContent); } exportToText(filePath) { const content = this.errors .map(error => this.formatTextLogLine(error)) .join('\n'); (0, fs_1.writeFileSync)(filePath, content); } generateSessionId() { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } generateErrorId() { return `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } generateIssueId() { return `issue_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Static utility methods */ /** * Create a default error logger */ static createDefault() { return new ErrorLogger(); } /** * Create a console-only error logger */ static createConsoleOnly() { return new ErrorLogger({ enableFileLogging: false, enableConsoleLogging: true, }); } /** * Create a file-only error logger */ static createFileOnly(logFilePath) { return new ErrorLogger({ enableFileLogging: true, enableConsoleLogging: false, logFilePath, }); } /** * Create a debug-level error logger */ static createDebug() { return new ErrorLogger({ minLevel: 'debug', includeStackTraces: true, }); } } exports.ErrorLogger = ErrorLogger; // Error level priorities for filtering ErrorLogger.LEVEL_PRIORITIES = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4, }; //# sourceMappingURL=error-logger.js.map