Add a robust debug logging system that captures application state, user interactions, and system diagnostics for enhanced troubleshooting and development experience. ## ✨ Features Added ### 🔍 **Multi-Source Data Capture** - **Console Logging**: Captures all console.log, console.warn, console.error - **Error Handling**: Intercepts JavaScript errors and unhandled promise rejections - **Network Monitoring**: Tracks all fetch requests with timing and status - **User Actions**: Records user interactions and UI events - **Terminal Activity**: Captures shell input/output with ANSI cleaning - **Performance Metrics**: Memory usage, page load times, paint timing ### 📊 **System Information Collection** - Platform detection (macOS, Windows, Linux) - Browser and viewport information - Git repository status (branch, commit, dirty state) - Application state (model, provider, workbench view) - Performance and memory statistics ### 🎯 **User Interface Integration** - **Avatar Dropdown**: "Download Debug Log" option with download icon - **Header Actions**: "Debug Log" button alongside existing "Report Bug" - **One-Click Download**: Generates comprehensive debug reports - **Error Handling**: Graceful degradation with user feedback ### 🔧 **Technical Implementation** - **Circular Buffers**: Memory-efficient storage with fixed capacity (1K entries) - **Lazy Loading**: Zero performance impact when disabled (default state) - **Debouncing**: Terminal logs debounced at 100ms to prevent spam - **JSON Safe**: Circular reference protection and depth limiting - **Async Operations**: Non-blocking debug operations ### 📁 **Files Modified** - `app/utils/debugLogger.ts` (1,284 lines) - Core debug logging utility - `app/utils/logger.ts` - Integration with existing logging system - `app/utils/shell.ts` - Terminal activity capture - `app/components/@settings/core/AvatarDropdown.tsx` - UI integration - `app/components/header/HeaderActionButtons.client.tsx` - Header button - `app/root.tsx` - Initialization and setup - `app/routes/api.git-info.ts` - Git information endpoint ## 🚀 **Benefits** - **Enhanced Debugging**: Comprehensive data collection for issue reproduction - **Performance Monitoring**: Built-in performance tracking and memory analysis - **User Support**: Easy debug log generation for support tickets - **Developer Experience**: Rich debugging data without performance penalty - **Production Ready**: Opt-in system with zero impact on regular users ## 🔒 **Security & Privacy** - Client-side only operation (no server transmission) - User-controlled data collection and export - No sensitive information captured automatically - Manual opt-in required for debug mode activation ## 📈 **Performance Impact** - **Disabled by Default**: No performance impact for regular users - **Lazy Initialization**: Components loaded only when needed - **Memory Bounded**: Fixed-size buffers prevent memory leaks - **Non-Blocking**: All operations are asynchronous - **Efficient Storage**: Circular buffers with automatic cleanup ## 🔄 **Integration Points** - Seamlessly integrates with existing `logger` utility - Compatible with current shell/terminal implementation - Works with existing error handling patterns - Maintains backward compatibility This implementation provides developers and users with powerful debugging capabilities while maintaining excellent performance and user experience.
163 lines
4.6 KiB
TypeScript
163 lines
4.6 KiB
TypeScript
export type DebugLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
import { Chalk } from 'chalk';
|
|
|
|
const chalk = new Chalk({ level: 3 });
|
|
|
|
type LoggerFunction = (...messages: any[]) => void;
|
|
|
|
interface Logger {
|
|
trace: LoggerFunction;
|
|
debug: LoggerFunction;
|
|
info: LoggerFunction;
|
|
warn: LoggerFunction;
|
|
error: LoggerFunction;
|
|
setLevel: (level: DebugLevel) => void;
|
|
}
|
|
|
|
let currentLevel: DebugLevel = import.meta.env.VITE_LOG_LEVEL || (import.meta.env.DEV ? 'debug' : 'info');
|
|
|
|
export const logger: Logger = {
|
|
trace: (...messages: any[]) => logWithDebugCapture('trace', undefined, messages),
|
|
debug: (...messages: any[]) => logWithDebugCapture('debug', undefined, messages),
|
|
info: (...messages: any[]) => logWithDebugCapture('info', undefined, messages),
|
|
warn: (...messages: any[]) => logWithDebugCapture('warn', undefined, messages),
|
|
error: (...messages: any[]) => logWithDebugCapture('error', undefined, messages),
|
|
setLevel,
|
|
};
|
|
|
|
export function createScopedLogger(scope: string): Logger {
|
|
return {
|
|
trace: (...messages: any[]) => logWithDebugCapture('trace', scope, messages),
|
|
debug: (...messages: any[]) => logWithDebugCapture('debug', scope, messages),
|
|
info: (...messages: any[]) => logWithDebugCapture('info', scope, messages),
|
|
warn: (...messages: any[]) => logWithDebugCapture('warn', scope, messages),
|
|
error: (...messages: any[]) => logWithDebugCapture('error', scope, messages),
|
|
setLevel,
|
|
};
|
|
}
|
|
|
|
function setLevel(level: DebugLevel) {
|
|
if ((level === 'trace' || level === 'debug') && import.meta.env.PROD) {
|
|
return;
|
|
}
|
|
|
|
currentLevel = level;
|
|
}
|
|
|
|
function log(level: DebugLevel, scope: string | undefined, messages: any[]) {
|
|
const levelOrder: DebugLevel[] = ['trace', 'debug', 'info', 'warn', 'error', 'none'];
|
|
|
|
if (levelOrder.indexOf(level) < levelOrder.indexOf(currentLevel)) {
|
|
return;
|
|
}
|
|
|
|
// If current level is 'none', don't log anything
|
|
if (currentLevel === 'none') {
|
|
return;
|
|
}
|
|
|
|
const allMessages = messages.reduce((acc, current) => {
|
|
if (acc.endsWith('\n')) {
|
|
return acc + current;
|
|
}
|
|
|
|
if (!acc) {
|
|
return current;
|
|
}
|
|
|
|
return `${acc} ${current}`;
|
|
}, '');
|
|
|
|
const labelBackgroundColor = getColorForLevel(level);
|
|
const labelTextColor = level === 'warn' ? '#000000' : '#FFFFFF';
|
|
|
|
const labelStyles = getLabelStyles(labelBackgroundColor, labelTextColor);
|
|
const scopeStyles = getLabelStyles('#77828D', 'white');
|
|
|
|
const styles = [labelStyles];
|
|
|
|
if (typeof scope === 'string') {
|
|
styles.push('', scopeStyles);
|
|
}
|
|
|
|
let labelText = formatText(` ${level.toUpperCase()} `, labelTextColor, labelBackgroundColor);
|
|
|
|
if (scope) {
|
|
labelText = `${labelText} ${formatText(` ${scope} `, '#FFFFFF', '77828D')}`;
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
console.log(`%c${level.toUpperCase()}${scope ? `%c %c${scope}` : ''}`, ...styles, allMessages);
|
|
} else {
|
|
console.log(`${labelText}`, allMessages);
|
|
}
|
|
}
|
|
|
|
function formatText(text: string, color: string, bg: string) {
|
|
return chalk.bgHex(bg)(chalk.hex(color)(text));
|
|
}
|
|
|
|
function getLabelStyles(color: string, textColor: string) {
|
|
return `background-color: ${color}; color: white; border: 4px solid ${color}; color: ${textColor};`;
|
|
}
|
|
|
|
function getColorForLevel(level: DebugLevel): string {
|
|
switch (level) {
|
|
case 'trace':
|
|
case 'debug': {
|
|
return '#77828D';
|
|
}
|
|
case 'info': {
|
|
return '#1389FD';
|
|
}
|
|
case 'warn': {
|
|
return '#FFDB6C';
|
|
}
|
|
case 'error': {
|
|
return '#EE4744';
|
|
}
|
|
default: {
|
|
return '#000000';
|
|
}
|
|
}
|
|
}
|
|
|
|
export const renderLogger = createScopedLogger('Render');
|
|
|
|
// Debug logging integration
|
|
let debugLogger: any = null;
|
|
|
|
// Lazy load debug logger to avoid circular dependencies
|
|
const getDebugLogger = () => {
|
|
if (!debugLogger && typeof window !== 'undefined') {
|
|
try {
|
|
// Use dynamic import asynchronously but don't block the function
|
|
import('./debugLogger')
|
|
.then(({ debugLogger: loggerInstance }) => {
|
|
debugLogger = loggerInstance;
|
|
})
|
|
.catch(() => {
|
|
// Debug logger not available, skip integration
|
|
});
|
|
} catch {
|
|
// Debug logger not available, skip integration
|
|
}
|
|
}
|
|
|
|
return debugLogger;
|
|
};
|
|
|
|
// Override the log function to also capture to debug logger
|
|
|
|
function logWithDebugCapture(level: DebugLevel, scope: string | undefined, messages: any[]) {
|
|
// Call original log function (the one that does the actual console logging)
|
|
log(level, scope, messages);
|
|
|
|
// Also capture to debug logger if available
|
|
const debug = getDebugLogger();
|
|
|
|
if (debug) {
|
|
debug.captureLog(level, scope, messages);
|
|
}
|
|
}
|