filetree-pro
Version:
A powerful file tree generator for VS Code and Cursor. Generate beautiful file trees in multiple formats with smart exclusions and custom configurations.
362 lines (331 loc) • 10.3 kB
text/typescript
/**
* Centralized error handling for the FileTree Pro extension.
* Provides consistent error logging, user notifications, and telemetry.
*
* @module errorHandler
* @author FileTree Pro Team
* @since 0.2.0
*/
import * as vscode from 'vscode';
/**
* Error severity levels for categorization and telemetry
*/
export enum ErrorSeverity {
/** Informational message, not an error */
INFO = 'info',
/** Warning that doesn't prevent operation */
WARNING = 'warning',
/** Error that prevents current operation */
ERROR = 'error',
/** Critical error that may affect extension stability */
CRITICAL = 'critical',
}
/**
* Error categories for better error tracking and debugging
*/
export enum ErrorCategory {
/** File system operation errors (read, write, access) */
FILE_SYSTEM = 'FileSystem',
/** Security validation errors (path traversal, ReDoS) */
SECURITY = 'Security',
/** API or network errors (Copilot, rate limiting) */
API = 'API',
/** Configuration or settings errors */
CONFIGURATION = 'Configuration',
/** User input validation errors */
VALIDATION = 'Validation',
/** Internal extension errors */
INTERNAL = 'Internal',
}
/**
* Structured error information for logging and telemetry
*/
export interface ErrorInfo {
/** Error message for display */
readonly message: string;
/** Error severity level */
readonly severity: ErrorSeverity;
/** Error category */
readonly category: ErrorCategory;
/** Original error object if available */
readonly originalError?: Error;
/** Additional context data */
readonly context?: Record<string, unknown>;
/** Timestamp when error occurred */
readonly timestamp: Date;
}
/**
* Centralized error handler with output channel and user notifications.
* Singleton pattern ensures consistent error handling across the extension.
*
* @example
* ```typescript
* const handler = ErrorHandler.getInstance();
* handler.handleError({
* message: 'Failed to read file',
* severity: ErrorSeverity.ERROR,
* category: ErrorCategory.FILE_SYSTEM,
* originalError: error,
* context: { path: '/path/to/file' }
* });
* ```
*/
export class ErrorHandler {
private static instance: ErrorHandler | null = null;
private outputChannel: vscode.OutputChannel | null = null;
private errorCount: Map<ErrorCategory, number> = new Map();
/**
* Private constructor for singleton pattern
*/
private constructor() {
// Initialize error counts
for (const category of Object.values(ErrorCategory)) {
this.errorCount.set(category, 0);
}
}
/**
* Gets the singleton instance of ErrorHandler.
* Lazy initialization ensures output channel is created only when needed.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @returns The singleton ErrorHandler instance
*/
public static getInstance(): ErrorHandler {
if (!ErrorHandler.instance) {
ErrorHandler.instance = new ErrorHandler();
}
return ErrorHandler.instance;
}
/**
* Initializes the output channel for error logging.
* Should be called during extension activation.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @param channelName - Optional custom channel name (defaults to 'FileTree Pro')
*/
public initialize(channelName: string = 'FileTree Pro'): void {
if (!this.outputChannel) {
this.outputChannel = vscode.window.createOutputChannel(channelName);
}
}
/**
* Handles an error with appropriate logging and user notification.
* Automatically determines notification type based on severity.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @param errorInfo - Structured error information
* @returns Promise that resolves when error is handled
*/
public async handleError(errorInfo: ErrorInfo): Promise<void> {
// Increment error count for telemetry
const currentCount = this.errorCount.get(errorInfo.category) || 0;
this.errorCount.set(errorInfo.category, currentCount + 1);
// Log to output channel
this.logToOutput(errorInfo);
// Show user notification based on severity
await this.showNotification(errorInfo);
// Log to console in development mode
if (process.env.NODE_ENV === 'development') {
console.error('[FileTree Pro]', errorInfo);
}
}
/**
* Logs error information to the output channel.
* Creates formatted log entry with timestamp, category, and context.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @param errorInfo - Error information to log
*/
private logToOutput(errorInfo: ErrorInfo): void {
if (!this.outputChannel) {
this.initialize();
}
const timestamp = errorInfo.timestamp.toISOString();
const category = errorInfo.category;
const severity = errorInfo.severity.toUpperCase();
// Build log message
let logMessage = `[${timestamp}] [${severity}] [${category}] ${errorInfo.message}`;
// Add original error stack if available
if (errorInfo.originalError) {
logMessage += `\n Original Error: ${errorInfo.originalError.message}`;
if (errorInfo.originalError.stack) {
logMessage += `\n Stack: ${errorInfo.originalError.stack}`;
}
}
// Add context if available
if (errorInfo.context && Object.keys(errorInfo.context).length > 0) {
logMessage += `\n Context: ${JSON.stringify(errorInfo.context, null, 2)}`;
}
this.outputChannel!.appendLine(logMessage);
this.outputChannel!.appendLine(''); // Add blank line for readability
}
/**
* Shows appropriate user notification based on error severity.
* Provides "Show Details" action to open output channel.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @param errorInfo - Error information to display
*/
private async showNotification(errorInfo: ErrorInfo): Promise<void> {
const message = `FileTree Pro: ${errorInfo.message}`;
const showDetailsAction = 'Show Details';
let selectedAction: string | undefined;
// Choose notification type based on severity
switch (errorInfo.severity) {
case ErrorSeverity.INFO:
selectedAction = await vscode.window.showInformationMessage(message, showDetailsAction);
break;
case ErrorSeverity.WARNING:
selectedAction = await vscode.window.showWarningMessage(message, showDetailsAction);
break;
case ErrorSeverity.ERROR:
case ErrorSeverity.CRITICAL:
selectedAction = await vscode.window.showErrorMessage(message, showDetailsAction);
break;
}
// Show output channel if user clicked "Show Details"
if (selectedAction === showDetailsAction && this.outputChannel) {
this.outputChannel.show();
}
}
/**
* Creates a standardized error info object for file system errors.
* Helper method for common file system error scenarios.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @param message - User-friendly error message
* @param error - Original error object
* @param context - Additional context (e.g., file path)
* @returns Structured error information
*/
public createFileSystemError(
message: string,
error?: Error,
context?: Record<string, unknown>
): ErrorInfo {
return {
message,
severity: ErrorSeverity.ERROR,
category: ErrorCategory.FILE_SYSTEM,
originalError: error,
context,
timestamp: new Date(),
};
}
/**
* Creates a standardized error info object for security validation errors.
* Helper method for security-related error scenarios.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @param message - User-friendly error message
* @param context - Additional context (e.g., invalid path)
* @returns Structured error information
*/
public createSecurityError(message: string, context?: Record<string, unknown>): ErrorInfo {
return {
message,
severity: ErrorSeverity.CRITICAL,
category: ErrorCategory.SECURITY,
context,
timestamp: new Date(),
};
}
/**
* Creates a standardized error info object for API errors.
* Helper method for API-related error scenarios.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @param message - User-friendly error message
* @param error - Original error object
* @param context - Additional context (e.g., API endpoint)
* @returns Structured error information
*/
public createApiError(
message: string,
error?: Error,
context?: Record<string, unknown>
): ErrorInfo {
return {
message,
severity: ErrorSeverity.WARNING,
category: ErrorCategory.API,
originalError: error,
context,
timestamp: new Date(),
};
}
/**
* Gets error statistics for telemetry.
* Provides count of errors by category for debugging.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* @returns Map of error counts by category
*/
public getErrorStatistics(): Map<ErrorCategory, number> {
return new Map(this.errorCount);
}
/**
* Resets error statistics.
* Useful for testing or periodic statistics reset.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
public resetStatistics(): void {
for (const category of Object.values(ErrorCategory)) {
this.errorCount.set(category, 0);
}
}
/**
* Disposes resources used by the error handler.
* Should be called during extension deactivation.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
public dispose(): void {
if (this.outputChannel) {
this.outputChannel.dispose();
this.outputChannel = null;
}
this.errorCount.clear();
}
/**
* Shows the output channel to the user.
* Useful for debugging or when user requests to see logs.
*
* Time Complexity: O(1)
* Space Complexity: O(1)
*/
public showOutputChannel(): void {
if (this.outputChannel) {
this.outputChannel.show();
}
}
}
/**
* Convenience function to get the ErrorHandler singleton instance.
*
* @returns The singleton ErrorHandler instance
*/
export function getErrorHandler(): ErrorHandler {
return ErrorHandler.getInstance();
}