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
JavaScript
"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