Skip to main content

Debugging Guide

This guide covers debugging techniques, tools, and best practices for Qirvo plugin development, including browser DevTools, logging strategies, and troubleshooting common issues.

Table of Contents

Debugging Setup

Development Environment

// Debug configuration for development
export class DebugConfig {
static readonly isDevelopment = process.env.NODE_ENV === 'development';
static readonly isProduction = process.env.NODE_ENV === 'production';
static readonly debugLevel = process.env.DEBUG_LEVEL || 'info';

static enableDebugMode(): void {
if (this.isDevelopment) {
// Enable React DevTools
if (typeof window !== 'undefined') {
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__?.onCommitFiberRoot =
(window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__?.onCommitFiberRoot || (() => {});
}

// Enable verbose logging
console.log('🐛 Debug mode enabled');

// Add debug utilities to global scope
(window as any).debugPlugin = this.createDebugUtilities();
}
}

private static createDebugUtilities() {
return {
logState: (component: any) => {
console.group('Component State');
console.log(component);
console.groupEnd();
},

logProps: (props: any) => {
console.group('Component Props');
console.table(props);
console.groupEnd();
},

logContext: (context: any) => {
console.group('Plugin Context');
console.log(context);
console.groupEnd();
},

measurePerformance: (name: string, fn: Function) => {
console.time(name);
const result = fn();
console.timeEnd(name);
return result;
}
};
}
}

Debug Logger

// Comprehensive logging system for debugging
export class DebugLogger {
private static instance: DebugLogger;
private logs: LogEntry[] = [];
private maxLogs: number = 1000;
private logLevel: LogLevel = LogLevel.INFO;

static getInstance(): DebugLogger {
if (!this.instance) {
this.instance = new DebugLogger();
}
return this.instance;
}

setLogLevel(level: LogLevel): void {
this.logLevel = level;
}

debug(message: string, data?: any): void {
this.log(LogLevel.DEBUG, message, data);
}

info(message: string, data?: any): void {
this.log(LogLevel.INFO, message, data);
}

warn(message: string, data?: any): void {
this.log(LogLevel.WARN, message, data);
}

error(message: string, error?: Error | any): void {
this.log(LogLevel.ERROR, message, error);
}

private log(level: LogLevel, message: string, data?: any): void {
if (level < this.logLevel) return;

const entry: LogEntry = {
timestamp: new Date(),
level,
message,
data,
stack: new Error().stack
};

this.logs.push(entry);

// Trim logs if exceeding max
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}

// Console output with styling
this.outputToConsole(entry);
}

private outputToConsole(entry: LogEntry): void {
const styles = {
[LogLevel.DEBUG]: 'color: #888',
[LogLevel.INFO]: 'color: #007acc',
[LogLevel.WARN]: 'color: #ff8c00',
[LogLevel.ERROR]: 'color: #ff4444; font-weight: bold'
};

const timestamp = entry.timestamp.toISOString().substr(11, 12);
const levelName = LogLevel[entry.level];

console.log(
`%c[${timestamp}] ${levelName}: ${entry.message}`,
styles[entry.level],
entry.data || ''
);

if (entry.level === LogLevel.ERROR && entry.data instanceof Error) {
console.error(entry.data);
}
}

getLogs(level?: LogLevel): LogEntry[] {
if (level !== undefined) {
return this.logs.filter(log => log.level >= level);
}
return [...this.logs];
}

exportLogs(): string {
return JSON.stringify(this.logs, null, 2);
}

clearLogs(): void {
this.logs = [];
}
}

enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3
}

interface LogEntry {
timestamp: Date;
level: LogLevel;
message: string;
data?: any;
stack?: string;
}

Browser DevTools

DevTools Integration

// DevTools integration for plugin debugging
export class DevToolsIntegration {
private static isEnabled = false;

static enable(): void {
if (this.isEnabled || !DebugConfig.isDevelopment) return;

this.isEnabled = true;
this.setupConsoleCommands();
this.setupPerformanceMarkers();
this.setupNetworkMonitoring();
}

private static setupConsoleCommands(): void {
// Add debug commands to console
(window as any).qirvoDebug = {
// Inspect plugin state
inspectPlugin: (pluginId: string) => {
const plugin = this.getPluginById(pluginId);
if (plugin) {
console.group(`🔍 Plugin: ${pluginId}`);
console.log('State:', plugin.getState?.());
console.log('Config:', plugin.getConfig?.());
console.log('Instance:', plugin);
console.groupEnd();
} else {
console.warn(`Plugin ${pluginId} not found`);
}
},

// List all plugins
listPlugins: () => {
const plugins = this.getAllPlugins();
console.table(plugins.map(p => ({
id: p.id,
name: p.name,
version: p.version,
state: p.getState?.() || 'unknown'
})));
},

// Monitor plugin events
monitorEvents: (pluginId: string) => {
const plugin = this.getPluginById(pluginId);
if (plugin && plugin.on) {
const events = ['enable', 'disable', 'config-change', 'error'];
events.forEach(event => {
plugin.on(event, (...args: any[]) => {
console.log(`📡 ${pluginId} event: ${event}`, args);
});
});
console.log(`Monitoring events for ${pluginId}`);
}
},

// Export debug information
exportDebugInfo: () => {
const debugInfo = {
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
plugins: this.getAllPlugins().map(p => ({
id: p.id,
name: p.name,
version: p.version,
state: p.getState?.()
})),
logs: DebugLogger.getInstance().getLogs(),
performance: this.getPerformanceMetrics()
};

console.log('Debug info exported:', debugInfo);
return debugInfo;
}
};

console.log('🛠️ Qirvo debug commands available: qirvoDebug');
}

private static setupPerformanceMarkers(): void {
// Add performance markers for plugin operations
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const url = args[0].toString();
performance.mark(`fetch-start-${url}`);

try {
const response = await originalFetch(...args);
performance.mark(`fetch-end-${url}`);
performance.measure(`fetch-${url}`, `fetch-start-${url}`, `fetch-end-${url}`);
return response;
} catch (error) {
performance.mark(`fetch-error-${url}`);
throw error;
}
};
}

private static setupNetworkMonitoring(): void {
// Monitor network requests
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.entryType === 'resource') {
const resourceEntry = entry as PerformanceResourceTiming;
if (resourceEntry.duration > 1000) { // Slow requests
console.warn(`🐌 Slow request: ${resourceEntry.name} (${resourceEntry.duration}ms)`);
}
}
});
});

observer.observe({ entryTypes: ['resource'] });
}

private static getPluginById(id: string): any {
// Implementation would get plugin from registry
return null;
}

private static getAllPlugins(): any[] {
// Implementation would get all plugins from registry
return [];
}

private static getPerformanceMetrics(): any {
return {
memory: (performance as any).memory,
navigation: performance.getEntriesByType('navigation')[0],
resources: performance.getEntriesByType('resource').length
};
}
}

React DevTools Integration

// React DevTools helpers for component debugging
export class ReactDebugTools {
static highlightComponent(component: React.ComponentType): React.ComponentType {
if (!DebugConfig.isDevelopment) return component;

return React.forwardRef((props: any, ref: any) => {
const [renderCount, setRenderCount] = useState(0);
const [lastProps, setLastProps] = useState(props);

useEffect(() => {
setRenderCount(prev => prev + 1);

// Log prop changes
if (JSON.stringify(props) !== JSON.stringify(lastProps)) {
console.log(`🔄 ${component.name} props changed:`, {
old: lastProps,
new: props
});
setLastProps(props);
}
});

useEffect(() => {
console.log(`🎨 ${component.name} mounted (render #${renderCount})`);

return () => {
console.log(`💀 ${component.name} unmounted`);
};
}, []);

return React.createElement(component, { ...props, ref });
});
}

static withPerformanceTracking<P>(
Component: React.ComponentType<P>
): React.ComponentType<P> {
return (props: P) => {
const renderStart = useRef<number>();

renderStart.current = performance.now();

useLayoutEffect(() => {
const renderTime = performance.now() - renderStart.current!;
if (renderTime > 16) { // Longer than one frame
console.warn(`⚠️ Slow render: ${Component.name} took ${renderTime.toFixed(2)}ms`);
}
});

return <Component {...props} />;
};
}

static logComponentTree(element: React.ReactElement, depth = 0): void {
const indent = ' '.repeat(depth);
const type = typeof element.type === 'string' ? element.type : element.type.name;

console.log(`${indent}${type}`, element.props);

if (element.props.children) {
React.Children.forEach(element.props.children, (child) => {
if (React.isValidElement(child)) {
this.logComponentTree(child, depth + 1);
}
});
}
}
}

Logging Strategies

Structured Logging

// Structured logging for better debugging
export class StructuredLogger {
private context: LogContext = {};

setContext(context: Partial<LogContext>): void {
this.context = { ...this.context, ...context };
}

logEvent(event: LogEvent): void {
const entry = {
...event,
context: this.context,
timestamp: new Date().toISOString(),
sessionId: this.getSessionId()
};

// Send to appropriate destination
if (DebugConfig.isDevelopment) {
this.logToConsole(entry);
} else {
this.logToService(entry);
}
}

private logToConsole(entry: LogEventEntry): void {
const { level, message, data, context } = entry;

console.group(`${level.toUpperCase()}: ${message}`);
if (data) console.log('Data:', data);
if (context) console.log('Context:', context);
console.groupEnd();
}

private async logToService(entry: LogEventEntry): Promise<void> {
try {
await fetch('/api/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(entry)
});
} catch (error) {
console.error('Failed to send log to service:', error);
}
}

private getSessionId(): string {
let sessionId = sessionStorage.getItem('debug-session-id');
if (!sessionId) {
sessionId = Math.random().toString(36).substr(2, 9);
sessionStorage.setItem('debug-session-id', sessionId);
}
return sessionId;
}
}

interface LogContext {
userId?: string;
pluginId?: string;
componentName?: string;
action?: string;
}

interface LogEvent {
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
data?: any;
tags?: string[];
}

interface LogEventEntry extends LogEvent {
context: LogContext;
timestamp: string;
sessionId: string;
}

Common Issues

Troubleshooting Guide

// Common debugging scenarios and solutions
export class TroubleshootingGuide {
static diagnosePluginIssues(plugin: any): DiagnosisReport {
const report: DiagnosisReport = {
issues: [],
warnings: [],
suggestions: []
};

// Check plugin state
if (!plugin.getState || plugin.getState() === 'error') {
report.issues.push({
type: 'state',
message: 'Plugin is in error state',
solution: 'Check plugin logs and restart if necessary'
});
}

// Check configuration
const config = plugin.getConfig?.();
if (!config || Object.keys(config).length === 0) {
report.warnings.push({
type: 'config',
message: 'Plugin has no configuration',
solution: 'Verify plugin configuration is properly set'
});
}

// Check required permissions
const requiredPermissions = plugin.getRequiredPermissions?.();
if (requiredPermissions && requiredPermissions.length > 0) {
// Check if permissions are granted
const grantedPermissions = plugin.getGrantedPermissions?.();
const missingPermissions = requiredPermissions.filter(
perm => !grantedPermissions?.includes(perm)
);

if (missingPermissions.length > 0) {
report.issues.push({
type: 'permissions',
message: `Missing permissions: ${missingPermissions.join(', ')}`,
solution: 'Grant required permissions in plugin settings'
});
}
}

// Check network connectivity
if (plugin.requiresNetwork?.()) {
this.checkNetworkConnectivity().then(isOnline => {
if (!isOnline) {
report.issues.push({
type: 'network',
message: 'No network connectivity',
solution: 'Check internet connection'
});
}
});
}

return report;
}

static async checkNetworkConnectivity(): Promise<boolean> {
try {
const response = await fetch('/api/health', {
method: 'HEAD',
cache: 'no-cache'
});
return response.ok;
} catch {
return false;
}
}

static debugRenderIssues(component: React.ComponentType): void {
console.group('🎨 Render Debugging');

// Check for common render issues
const issues = [
'Infinite re-renders',
'Missing dependencies in useEffect',
'Expensive calculations in render',
'Unnecessary re-renders'
];

console.log('Common render issues to check:', issues);

// Add render tracking
const WrappedComponent = ReactDebugTools.withPerformanceTracking(component);
console.log('Performance tracking enabled for component');

console.groupEnd();
}

static debugStateIssues(hook: any): void {
console.group('📊 State Debugging');

// Log state changes
const originalSetState = hook.setState;
hook.setState = (newState: any) => {
console.log('State change:', {
from: hook.state,
to: newState
});
return originalSetState(newState);
};

console.groupEnd();
}
}

interface DiagnosisReport {
issues: Issue[];
warnings: Issue[];
suggestions: string[];
}

interface Issue {
type: string;
message: string;
solution: string;
}

Performance Debugging

Performance Profiler

// Performance debugging and profiling
export class PerformanceProfiler {
private profiles: Map<string, PerformanceProfile> = new Map();
private isRecording = false;

startProfiling(name: string): void {
if (this.isRecording) {
console.warn('Already recording a profile');
return;
}

this.isRecording = true;
const profile: PerformanceProfile = {
name,
startTime: performance.now(),
endTime: 0,
marks: [],
measures: [],
memorySnapshots: []
};

this.profiles.set(name, profile);
performance.mark(`profile-${name}-start`);

// Take initial memory snapshot
if ('memory' in performance) {
profile.memorySnapshots.push({
timestamp: performance.now(),
used: (performance as any).memory.usedJSHeapSize,
total: (performance as any).memory.totalJSHeapSize
});
}

console.log(`🎯 Started profiling: ${name}`);
}

addMark(name: string, label?: string): void {
if (!this.isRecording) return;

const markName = `mark-${name}`;
performance.mark(markName);

const activeProfile = Array.from(this.profiles.values())
.find(p => p.endTime === 0);

if (activeProfile) {
activeProfile.marks.push({
name: markName,
label: label || name,
timestamp: performance.now()
});
}
}

stopProfiling(): PerformanceProfile | null {
if (!this.isRecording) return null;

const activeProfile = Array.from(this.profiles.values())
.find(p => p.endTime === 0);

if (!activeProfile) return null;

activeProfile.endTime = performance.now();
performance.mark(`profile-${activeProfile.name}-end`);
performance.measure(
`profile-${activeProfile.name}`,
`profile-${activeProfile.name}-start`,
`profile-${activeProfile.name}-end`
);

// Take final memory snapshot
if ('memory' in performance) {
activeProfile.memorySnapshots.push({
timestamp: performance.now(),
used: (performance as any).memory.usedJSHeapSize,
total: (performance as any).memory.totalJSHeapSize
});
}

this.isRecording = false;

console.log(`✅ Stopped profiling: ${activeProfile.name}`);
this.logProfileResults(activeProfile);

return activeProfile;
}

private logProfileResults(profile: PerformanceProfile): void {
const duration = profile.endTime - profile.startTime;

console.group(`📊 Profile Results: ${profile.name}`);
console.log(`Duration: ${duration.toFixed(2)}ms`);

if (profile.marks.length > 0) {
console.log('Marks:');
profile.marks.forEach(mark => {
const relativeTime = mark.timestamp - profile.startTime;
console.log(` ${mark.label}: ${relativeTime.toFixed(2)}ms`);
});
}

if (profile.memorySnapshots.length >= 2) {
const initial = profile.memorySnapshots[0];
const final = profile.memorySnapshots[profile.memorySnapshots.length - 1];
const memoryDiff = final.used - initial.used;

console.log(`Memory change: ${(memoryDiff / 1024 / 1024).toFixed(2)}MB`);
}

console.groupEnd();
}

getProfile(name: string): PerformanceProfile | undefined {
return this.profiles.get(name);
}

getAllProfiles(): PerformanceProfile[] {
return Array.from(this.profiles.values());
}
}

interface PerformanceProfile {
name: string;
startTime: number;
endTime: number;
marks: PerformanceMark[];
measures: PerformanceMeasure[];
memorySnapshots: MemorySnapshot[];
}

interface PerformanceMark {
name: string;
label: string;
timestamp: number;
}

interface MemorySnapshot {
timestamp: number;
used: number;
total: number;
}

Production Debugging

Remote Debugging

// Production debugging capabilities
export class ProductionDebugger {
private static instance: ProductionDebugger;
private errorReporter: ErrorReporter;
private sessionRecorder: SessionRecorder;

static getInstance(): ProductionDebugger {
if (!this.instance) {
this.instance = new ProductionDebugger();
}
return this.instance;
}

constructor() {
this.errorReporter = new ErrorReporter();
this.sessionRecorder = new SessionRecorder();
this.setupErrorHandling();
}

private setupErrorHandling(): void {
// Global error handler
window.addEventListener('error', (event) => {
this.reportError(event.error, {
type: 'global-error',
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
});

// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', (event) => {
this.reportError(new Error(event.reason), {
type: 'unhandled-promise-rejection'
});
});
}

async reportError(error: Error, context: any = {}): Promise<void> {
const errorReport = {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
sessionId: this.sessionRecorder.getSessionId(),
breadcrumbs: this.sessionRecorder.getBreadcrumbs()
};

await this.errorReporter.send(errorReport);
}

enableRemoteDebugging(debugKey: string): void {
if (!DebugConfig.isProduction) return;

// Enable remote debugging with secure key
(window as any).__QIRVO_REMOTE_DEBUG__ = {
key: debugKey,
getLogs: () => DebugLogger.getInstance().getLogs(),
getState: () => this.getApplicationState(),
executeCommand: (command: string) => this.executeDebugCommand(command)
};

console.log('🔐 Remote debugging enabled');
}

private getApplicationState(): any {
return {
plugins: this.getPluginStates(),
performance: this.getPerformanceMetrics(),
errors: this.errorReporter.getRecentErrors(),
memory: this.getMemoryInfo()
};
}

private executeDebugCommand(command: string): any {
// Safely execute debug commands
const allowedCommands = [
'getState',
'getLogs',
'clearLogs',
'getPerformance'
];

if (!allowedCommands.includes(command)) {
throw new Error('Command not allowed');
}

switch (command) {
case 'getState':
return this.getApplicationState();
case 'getLogs':
return DebugLogger.getInstance().getLogs();
case 'clearLogs':
DebugLogger.getInstance().clearLogs();
return 'Logs cleared';
case 'getPerformance':
return this.getPerformanceMetrics();
default:
throw new Error('Unknown command');
}
}

private getPluginStates(): any[] {
// Implementation would get plugin states
return [];
}

private getPerformanceMetrics(): any {
return {
memory: (performance as any).memory,
timing: performance.timing,
navigation: performance.navigation
};
}

private getMemoryInfo(): any {
if ('memory' in performance) {
return (performance as any).memory;
}
return null;
}
}

class ErrorReporter {
private errors: any[] = [];

async send(errorReport: any): Promise<void> {
this.errors.push(errorReport);

try {
await fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorReport)
});
} catch (error) {
console.error('Failed to report error:', error);
}
}

getRecentErrors(): any[] {
return this.errors.slice(-10);
}
}

class SessionRecorder {
private sessionId: string;
private breadcrumbs: any[] = [];

constructor() {
this.sessionId = this.generateSessionId();
this.startRecording();
}

private generateSessionId(): string {
return Math.random().toString(36).substr(2, 9);
}

private startRecording(): void {
// Record user interactions
['click', 'input', 'submit'].forEach(eventType => {
document.addEventListener(eventType, (event) => {
this.addBreadcrumb({
type: 'user-interaction',
event: eventType,
target: this.getElementSelector(event.target as Element),
timestamp: Date.now()
});
});
});
}

addBreadcrumb(breadcrumb: any): void {
this.breadcrumbs.push(breadcrumb);

// Keep only last 50 breadcrumbs
if (this.breadcrumbs.length > 50) {
this.breadcrumbs = this.breadcrumbs.slice(-50);
}
}

getSessionId(): string {
return this.sessionId;
}

getBreadcrumbs(): any[] {
return [...this.breadcrumbs];
}

private getElementSelector(element: Element): string {
if (element.id) return `#${element.id}`;
if (element.className) return `.${element.className.split(' ')[0]}`;
return element.tagName.toLowerCase();
}
}

This debugging guide provides comprehensive tools and techniques for debugging Qirvo plugins in both development and production environments.


Next: Build Process