javascript
exercises
exercises.jsā”javascript
/**
* 20.4 Debugging Techniques - Exercises
*
* Practice debugging and building debugging tools
*/
// ============================================
// EXERCISE 1: Create a Debug Logger
// ============================================
/**
* Create a comprehensive debug logger with:
* - Log levels (trace, debug, info, warn, error)
* - Namespace support
* - Enable/disable by namespace pattern
* - Timestamps
* - Call stack capture
*/
class DebugLogger {
// Your implementation here
}
/*
// SOLUTION:
class DebugLogger {
static LEVELS = {
TRACE: 0,
DEBUG: 1,
INFO: 2,
WARN: 3,
ERROR: 4,
SILENT: 5
};
static currentLevel = DebugLogger.LEVELS.DEBUG;
static enabledPatterns = new Set(['*']);
static disabledPatterns = new Set();
static output = console;
constructor(namespace) {
this.namespace = namespace;
this.color = this.hashToColor(namespace);
}
hashToColor(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
const hue = Math.abs(hash) % 360;
return `hsl(${hue}, 70%, 45%)`;
}
isEnabled() {
// Check if explicitly disabled
for (const pattern of DebugLogger.disabledPatterns) {
if (this.matchPattern(pattern)) return false;
}
// Check if enabled
for (const pattern of DebugLogger.enabledPatterns) {
if (this.matchPattern(pattern)) return true;
}
return false;
}
matchPattern(pattern) {
if (pattern === '*') return true;
if (pattern === this.namespace) return true;
// Wildcard matching
if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
return this.namespace.startsWith(prefix);
}
return false;
}
formatMessage(level, args) {
const timestamp = new Date().toISOString();
const prefix = `[${timestamp}] [${level}] [${this.namespace}]`;
return { prefix, args };
}
log(level, levelNum, ...args) {
if (!this.isEnabled()) return;
if (levelNum < DebugLogger.currentLevel) return;
const { prefix } = this.formatMessage(level, args);
// Browser styling
if (typeof window !== 'undefined') {
const style = `color: ${this.color}; font-weight: bold;`;
DebugLogger.output.log(`%c${prefix}`, style, ...args);
} else {
DebugLogger.output.log(prefix, ...args);
}
}
trace(...args) {
this.log('TRACE', DebugLogger.LEVELS.TRACE, ...args);
if (this.isEnabled() && DebugLogger.currentLevel <= DebugLogger.LEVELS.TRACE) {
console.trace();
}
}
debug(...args) {
this.log('DEBUG', DebugLogger.LEVELS.DEBUG, ...args);
}
info(...args) {
this.log('INFO', DebugLogger.LEVELS.INFO, ...args);
}
warn(...args) {
this.log('WARN', DebugLogger.LEVELS.WARN, ...args);
}
error(...args) {
this.log('ERROR', DebugLogger.LEVELS.ERROR, ...args);
}
// Timing support
time(label) {
this._timers = this._timers || {};
this._timers[label] = performance.now();
}
timeEnd(label) {
if (!this._timers?.[label]) return;
const duration = performance.now() - this._timers[label];
this.debug(`${label}: ${duration.toFixed(2)}ms`);
delete this._timers[label];
}
// Grouping
group(label) {
if (!this.isEnabled()) return;
console.group(`[${this.namespace}] ${label}`);
}
groupCollapsed(label) {
if (!this.isEnabled()) return;
console.groupCollapsed(`[${this.namespace}] ${label}`);
}
groupEnd() {
if (!this.isEnabled()) return;
console.groupEnd();
}
// Create child logger
extend(suffix) {
return new DebugLogger(`${this.namespace}:${suffix}`);
}
// Static configuration
static setLevel(level) {
if (typeof level === 'string') {
DebugLogger.currentLevel = DebugLogger.LEVELS[level.toUpperCase()] ?? 0;
} else {
DebugLogger.currentLevel = level;
}
}
static enable(pattern) {
DebugLogger.enabledPatterns.add(pattern);
}
static disable(pattern) {
DebugLogger.disabledPatterns.add(pattern);
}
static enableAll() {
DebugLogger.enabledPatterns.add('*');
DebugLogger.disabledPatterns.clear();
}
static disableAll() {
DebugLogger.enabledPatterns.clear();
DebugLogger.disabledPatterns.add('*');
}
static reset() {
DebugLogger.enabledPatterns = new Set(['*']);
DebugLogger.disabledPatterns = new Set();
DebugLogger.currentLevel = DebugLogger.LEVELS.DEBUG;
}
}
// Test the logger
function testDebugLogger() {
console.log('=== Debug Logger Tests ===\n');
const appLog = new DebugLogger('app');
const dbLog = new DebugLogger('app:database');
const authLog = new DebugLogger('app:auth');
// Test basic logging
appLog.info('Application started');
appLog.debug('Debug message');
appLog.warn('Warning message');
appLog.error('Error message');
console.log('ā Basic logging works');
// Test levels
DebugLogger.setLevel('WARN');
appLog.debug('This should NOT appear');
appLog.warn('This should appear');
DebugLogger.setLevel('DEBUG');
console.log('ā Level filtering works');
// Test namespaces
DebugLogger.disable('app:database');
dbLog.info('This should NOT appear');
authLog.info('This should appear');
DebugLogger.reset();
console.log('ā Namespace filtering works');
// Test timing
appLog.time('operation');
for (let i = 0; i < 100000; i++) {}
appLog.timeEnd('operation');
console.log('ā Timing works');
// Test extend
const subLog = appLog.extend('submodule');
subLog.info('From submodule');
console.log('ā Extend works');
console.log('\n=== Debug Logger Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 2: Build an Error Tracker
// ============================================
/**
* Create an error tracking system that:
* - Captures errors with full context
* - Parses stack traces
* - Groups similar errors
* - Generates reports
*/
class ErrorTracker {
// Your implementation here
}
/*
// SOLUTION:
class ErrorTracker {
constructor(options = {}) {
this.errors = [];
this.maxErrors = options.maxErrors || 100;
this.errorGroups = new Map();
this.handlers = [];
}
capture(error, context = {}) {
const errorInfo = {
id: this.generateId(),
timestamp: Date.now(),
isoTime: new Date().toISOString(),
message: error.message,
name: error.name,
stack: error.stack,
parsedStack: this.parseStack(error.stack),
context,
fingerprint: this.getFingerprint(error)
};
// Add browser/node info
if (typeof window !== 'undefined') {
errorInfo.url = window.location.href;
errorInfo.userAgent = navigator.userAgent;
}
this.errors.push(errorInfo);
// Group by fingerprint
if (!this.errorGroups.has(errorInfo.fingerprint)) {
this.errorGroups.set(errorInfo.fingerprint, []);
}
this.errorGroups.get(errorInfo.fingerprint).push(errorInfo);
// Limit stored errors
if (this.errors.length > this.maxErrors) {
this.errors.shift();
}
// Notify handlers
this.handlers.forEach(h => h(errorInfo));
return errorInfo;
}
generateId() {
return 'err_' + Math.random().toString(36).substr(2, 9);
}
parseStack(stack) {
if (!stack) return [];
const lines = stack.split('\n').slice(1);
return lines.map(line => {
// Chrome/Node format: at functionName (file:line:column)
let match = line.match(/at\s+(.+?)\s+\((.+):(\d+):(\d+)\)/);
if (match) {
return {
function: match[1],
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4])
};
}
// Anonymous function: at file:line:column
match = line.match(/at\s+(.+):(\d+):(\d+)/);
if (match) {
return {
function: '<anonymous>',
file: match[1],
line: parseInt(match[2]),
column: parseInt(match[3])
};
}
// Firefox format
match = line.match(/(.*)@(.+):(\d+):(\d+)/);
if (match) {
return {
function: match[1] || '<anonymous>',
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4])
};
}
return { raw: line.trim() };
}).filter(frame => frame.file || frame.raw);
}
getFingerprint(error) {
// Create fingerprint from error name and first stack frame
const stack = this.parseStack(error.stack);
const firstFrame = stack[0] || {};
return `${error.name}:${error.message.slice(0, 50)}:${firstFrame.file || ''}:${firstFrame.line || ''}`;
}
onError(handler) {
this.handlers.push(handler);
return () => {
const idx = this.handlers.indexOf(handler);
if (idx !== -1) this.handlers.splice(idx, 1);
};
}
getErrors(options = {}) {
let errors = [...this.errors];
if (options.name) {
errors = errors.filter(e => e.name === options.name);
}
if (options.since) {
errors = errors.filter(e => e.timestamp >= options.since);
}
if (options.limit) {
errors = errors.slice(-options.limit);
}
return errors;
}
getGroups() {
const groups = [];
for (const [fingerprint, errors] of this.errorGroups) {
groups.push({
fingerprint,
count: errors.length,
sample: errors[0],
lastSeen: errors[errors.length - 1].timestamp
});
}
return groups.sort((a, b) => b.count - a.count);
}
generateReport() {
const groups = this.getGroups();
const total = this.errors.length;
const unique = groups.length;
const byType = {};
for (const error of this.errors) {
byType[error.name] = (byType[error.name] || 0) + 1;
}
return {
summary: {
total,
unique,
timeRange: {
start: this.errors[0]?.isoTime,
end: this.errors[this.errors.length - 1]?.isoTime
}
},
byType,
topErrors: groups.slice(0, 5).map(g => ({
message: g.sample.message,
count: g.count,
lastSeen: new Date(g.lastSeen).toISOString()
})),
generatedAt: new Date().toISOString()
};
}
clear() {
this.errors = [];
this.errorGroups.clear();
}
// Global error handler
install() {
if (typeof window !== 'undefined') {
window.addEventListener('error', (event) => {
this.capture(event.error || new Error(event.message), {
type: 'uncaught',
filename: event.filename,
lineno: event.lineno
});
});
window.addEventListener('unhandledrejection', (event) => {
const error = event.reason instanceof Error ?
event.reason :
new Error(String(event.reason));
this.capture(error, { type: 'unhandledrejection' });
});
}
if (typeof process !== 'undefined') {
process.on('uncaughtException', (error) => {
this.capture(error, { type: 'uncaughtException' });
});
process.on('unhandledRejection', (reason) => {
const error = reason instanceof Error ?
reason :
new Error(String(reason));
this.capture(error, { type: 'unhandledRejection' });
});
}
}
}
// Test the error tracker
function testErrorTracker() {
console.log('=== Error Tracker Tests ===\n');
const tracker = new ErrorTracker();
// Test capture
try {
throw new Error('Test error 1');
} catch (e) {
tracker.capture(e, { userId: 1 });
}
try {
throw new TypeError('Type mismatch');
} catch (e) {
tracker.capture(e);
}
try {
throw new Error('Test error 1'); // Same as first
} catch (e) {
tracker.capture(e, { userId: 2 });
}
console.log('Captured errors:', tracker.errors.length);
console.assert(tracker.errors.length === 3, 'Should have 3 errors');
console.log('ā Error capture works');
// Test grouping
const groups = tracker.getGroups();
console.log('Error groups:', groups.length);
console.assert(groups.length === 2, 'Should have 2 groups');
console.log('ā Error grouping works');
// Test report
const report = tracker.generateReport();
console.log('\nError Report:');
console.log(JSON.stringify(report, null, 2));
console.log('ā Report generation works');
console.log('\n=== Error Tracker Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 3: Create a Performance Profiler
// ============================================
/**
* Build a performance profiler that:
* - Measures function execution time
* - Tracks async operations
* - Computes statistics
* - Identifies slow operations
*/
class PerformanceProfiler {
// Your implementation here
}
/*
// SOLUTION:
class PerformanceProfiler {
constructor() {
this.measurements = new Map();
this.thresholds = {
slow: 100,
verySlow: 500
};
}
// Wrap function to profile it
profile(fn, name) {
const profiler = this;
const label = name || fn.name || 'anonymous';
return function(...args) {
const start = performance.now();
try {
const result = fn.apply(this, args);
if (result instanceof Promise) {
return result.finally(() => {
profiler.record(label, performance.now() - start);
});
}
profiler.record(label, performance.now() - start);
return result;
} catch (e) {
profiler.record(label, performance.now() - start);
throw e;
}
};
}
// Profile a block of code
measure(label, fn) {
const start = performance.now();
try {
const result = fn();
if (result instanceof Promise) {
return result.finally(() => {
this.record(label, performance.now() - start);
});
}
this.record(label, performance.now() - start);
return result;
} catch (e) {
this.record(label, performance.now() - start);
throw e;
}
}
// Manual timing
start(label) {
this._starts = this._starts || {};
this._starts[label] = performance.now();
}
end(label) {
if (!this._starts?.[label]) return null;
const duration = performance.now() - this._starts[label];
delete this._starts[label];
this.record(label, duration);
return duration;
}
record(label, duration) {
if (!this.measurements.has(label)) {
this.measurements.set(label, []);
}
this.measurements.get(label).push({
duration,
timestamp: Date.now(),
slow: duration > this.thresholds.slow,
verySlow: duration > this.thresholds.verySlow
});
}
getStats(label) {
const data = this.measurements.get(label);
if (!data || data.length === 0) return null;
const durations = data.map(d => d.duration).sort((a, b) => a - b);
const sum = durations.reduce((a, b) => a + b, 0);
const len = durations.length;
return {
name: label,
count: len,
total: sum,
mean: sum / len,
min: durations[0],
max: durations[len - 1],
median: durations[Math.floor(len / 2)],
p90: durations[Math.floor(len * 0.9)],
p95: durations[Math.floor(len * 0.95)],
p99: durations[Math.floor(len * 0.99)],
stdDev: Math.sqrt(
durations.reduce((sq, d) => sq + Math.pow(d - sum/len, 2), 0) / len
),
slowCount: data.filter(d => d.slow).length,
verySlowCount: data.filter(d => d.verySlow).length
};
}
getAllStats() {
const stats = {};
for (const label of this.measurements.keys()) {
stats[label] = this.getStats(label);
}
return stats;
}
getSlowOperations() {
const slow = [];
for (const [label, data] of this.measurements) {
const slowOps = data.filter(d => d.slow);
if (slowOps.length > 0) {
slow.push({
label,
count: slowOps.length,
maxDuration: Math.max(...slowOps.map(d => d.duration))
});
}
}
return slow.sort((a, b) => b.maxDuration - a.maxDuration);
}
report() {
const allStats = this.getAllStats();
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
console.log('ā Performance Report ā');
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
const rows = Object.values(allStats).map(s => ({
Name: s.name,
Count: s.count,
'Mean (ms)': s.mean.toFixed(2),
'Min (ms)': s.min.toFixed(2),
'Max (ms)': s.max.toFixed(2),
'P95 (ms)': s.p95?.toFixed(2) || '-',
'Slow': s.slowCount
}));
console.table(rows);
const slowOps = this.getSlowOperations();
if (slowOps.length > 0) {
console.log('\nā ļø Slow Operations:');
console.table(slowOps);
}
}
reset() {
this.measurements.clear();
}
setThresholds(slow, verySlow) {
this.thresholds = { slow, verySlow };
}
}
// Test the profiler
async function testPerformanceProfiler() {
console.log('=== Performance Profiler Tests ===\n');
const profiler = new PerformanceProfiler();
profiler.setThresholds(10, 50);
// Test profile wrapper
function slowFn(n) {
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i);
}
return result;
}
const profiledFn = profiler.profile(slowFn, 'slowFn');
for (let i = 0; i < 5; i++) {
profiledFn(100000 + i * 50000);
}
console.log('ā Profile wrapper works');
// Test measure
profiler.measure('inline-op', () => {
let sum = 0;
for (let i = 0; i < 50000; i++) sum += i;
return sum;
});
console.log('ā Measure works');
// Test async
const asyncFn = profiler.profile(async () => {
await new Promise(r => setTimeout(r, 15));
return 'done';
}, 'asyncFn');
await asyncFn();
console.log('ā Async profiling works');
// Test manual timing
profiler.start('manual');
for (let i = 0; i < 100000; i++) {}
profiler.end('manual');
console.log('ā Manual timing works');
// Show stats
const stats = profiler.getStats('slowFn');
console.log('\nslowFn stats:', {
count: stats.count,
mean: stats.mean.toFixed(2) + 'ms',
min: stats.min.toFixed(2) + 'ms',
max: stats.max.toFixed(2) + 'ms'
});
// Show report
profiler.report();
console.log('\n=== Performance Profiler Tests Complete ===\n');
}
*/
// ============================================
// EXERCISE 4: Debug Helper Functions
// ============================================
/**
* Implement these debugging utilities:
* 1. deepInspect - Deep object inspection with circular reference handling
* 2. diff - Compare two objects and show differences
* 3. traceFunction - Wrap a function to log all calls
* 4. watchProperty - Monitor property access/changes
*/
function deepInspect(obj, options) {
// Your implementation
}
function diff(obj1, obj2) {
// Your implementation
}
function traceFunction(fn, name) {
// Your implementation
}
function watchProperty(obj, prop) {
// Your implementation
}
/*
// SOLUTION:
function deepInspect(obj, options = {}) {
const { depth = 5, showHidden = false, colors = true } = options;
const seen = new WeakSet();
function format(value, currentDepth, indent = '') {
if (currentDepth > depth) return '[Max depth]';
// Primitives
if (value === null) return 'null';
if (value === undefined) return 'undefined';
if (typeof value === 'boolean') return String(value);
if (typeof value === 'number') return String(value);
if (typeof value === 'string') return `"${value}"`;
if (typeof value === 'symbol') return value.toString();
if (typeof value === 'function') {
return `[Function: ${value.name || 'anonymous'}]`;
}
if (typeof value === 'bigint') return value.toString() + 'n';
// Circular reference check
if (seen.has(value)) return '[Circular]';
seen.add(value);
// Special objects
if (value instanceof Date) return value.toISOString();
if (value instanceof RegExp) return value.toString();
if (value instanceof Error) {
return `${value.name}: ${value.message}`;
}
if (value instanceof Map) {
const entries = Array.from(value.entries())
.map(([k, v]) => `${format(k, currentDepth + 1)} => ${format(v, currentDepth + 1)}`);
return `Map(${value.size}) { ${entries.join(', ')} }`;
}
if (value instanceof Set) {
const items = Array.from(value)
.map(v => format(v, currentDepth + 1));
return `Set(${value.size}) { ${items.join(', ')} }`;
}
// Arrays
if (Array.isArray(value)) {
if (value.length === 0) return '[]';
const items = value.map((v, i) =>
`${indent} ${format(v, currentDepth + 1, indent + ' ')}`
);
return `[\n${items.join(',\n')}\n${indent}]`;
}
// Objects
const keys = showHidden ?
Object.getOwnPropertyNames(value) :
Object.keys(value);
if (keys.length === 0) return '{}';
const pairs = keys.map(k => {
const v = value[k];
return `${indent} ${k}: ${format(v, currentDepth + 1, indent + ' ')}`;
});
return `{\n${pairs.join(',\n')}\n${indent}}`;
}
return format(obj, 0);
}
function diff(obj1, obj2, path = '') {
const differences = [];
function compare(a, b, currentPath) {
// Same reference or both primitive and equal
if (a === b) return;
// Type mismatch
if (typeof a !== typeof b) {
differences.push({
path: currentPath || 'root',
type: 'type',
from: typeof a,
to: typeof b,
oldValue: a,
newValue: b
});
return;
}
// Null checks
if (a === null || b === null) {
differences.push({
path: currentPath || 'root',
type: 'value',
oldValue: a,
newValue: b
});
return;
}
// Primitives
if (typeof a !== 'object') {
differences.push({
path: currentPath || 'root',
type: 'value',
oldValue: a,
newValue: b
});
return;
}
// Arrays
if (Array.isArray(a) && Array.isArray(b)) {
const maxLen = Math.max(a.length, b.length);
for (let i = 0; i < maxLen; i++) {
if (i >= a.length) {
differences.push({
path: `${currentPath}[${i}]`,
type: 'added',
newValue: b[i]
});
} else if (i >= b.length) {
differences.push({
path: `${currentPath}[${i}]`,
type: 'removed',
oldValue: a[i]
});
} else {
compare(a[i], b[i], `${currentPath}[${i}]`);
}
}
return;
}
// Objects
const allKeys = new Set([...Object.keys(a), ...Object.keys(b)]);
for (const key of allKeys) {
const newPath = currentPath ? `${currentPath}.${key}` : key;
if (!(key in a)) {
differences.push({
path: newPath,
type: 'added',
newValue: b[key]
});
} else if (!(key in b)) {
differences.push({
path: newPath,
type: 'removed',
oldValue: a[key]
});
} else {
compare(a[key], b[key], newPath);
}
}
}
compare(obj1, obj2, path);
return {
hasDifferences: differences.length > 0,
count: differences.length,
differences,
summary() {
if (differences.length === 0) return 'Objects are equal';
return differences.map(d => {
switch (d.type) {
case 'added':
return `+ ${d.path}: ${JSON.stringify(d.newValue)}`;
case 'removed':
return `- ${d.path}: ${JSON.stringify(d.oldValue)}`;
case 'value':
return `~ ${d.path}: ${JSON.stringify(d.oldValue)} ā ${JSON.stringify(d.newValue)}`;
case 'type':
return `! ${d.path}: type changed ${d.from} ā ${d.to}`;
default:
return `? ${d.path}`;
}
}).join('\n');
}
};
}
function traceFunction(fn, name) {
const label = name || fn.name || 'anonymous';
let callCount = 0;
const traced = function(...args) {
const callId = ++callCount;
const indent = ' '.repeat(traced._depth || 0);
console.log(`${indent}ā ${label}(${args.map(a => JSON.stringify(a)).join(', ')}) [call #${callId}]`);
traced._depth = (traced._depth || 0) + 1;
const start = performance.now();
try {
const result = fn.apply(this, args);
if (result instanceof Promise) {
return result.then(r => {
const duration = (performance.now() - start).toFixed(2);
console.log(`${indent}ā ${label} returned: ${JSON.stringify(r)} (${duration}ms)`);
traced._depth--;
return r;
}).catch(e => {
console.log(`${indent}ā ${label} threw: ${e.message}`);
traced._depth--;
throw e;
});
}
const duration = (performance.now() - start).toFixed(2);
console.log(`${indent}ā ${label} returned: ${JSON.stringify(result)} (${duration}ms)`);
traced._depth--;
return result;
} catch (e) {
console.log(`${indent}ā ${label} threw: ${e.message}`);
traced._depth--;
throw e;
}
};
traced.getCallCount = () => callCount;
traced.reset = () => { callCount = 0; };
return traced;
}
function watchProperty(obj, prop) {
const watchers = {
get: [],
set: []
};
let value = obj[prop];
const descriptor = Object.getOwnPropertyDescriptor(obj, prop) || {
configurable: true,
enumerable: true
};
Object.defineProperty(obj, prop, {
configurable: true,
enumerable: descriptor.enumerable,
get() {
const stack = new Error().stack?.split('\n')[2] || '';
watchers.get.forEach(fn => fn(value, stack));
console.log(`[GET] ${prop} = ${JSON.stringify(value)}`);
return value;
},
set(newValue) {
const oldValue = value;
value = newValue;
const stack = new Error().stack?.split('\n')[2] || '';
watchers.set.forEach(fn => fn(newValue, oldValue, stack));
console.log(`[SET] ${prop}: ${JSON.stringify(oldValue)} ā ${JSON.stringify(newValue)}`);
}
});
return {
onGet(fn) {
watchers.get.push(fn);
return () => {
const idx = watchers.get.indexOf(fn);
if (idx !== -1) watchers.get.splice(idx, 1);
};
},
onSet(fn) {
watchers.set.push(fn);
return () => {
const idx = watchers.set.indexOf(fn);
if (idx !== -1) watchers.set.splice(idx, 1);
};
},
unwatch() {
Object.defineProperty(obj, prop, {
...descriptor,
value,
writable: true
});
}
};
}
// Test the utilities
function testDebugUtilities() {
console.log('=== Debug Utilities Tests ===\n');
// Test deepInspect
console.log('--- deepInspect ---');
const circular = { a: 1 };
circular.self = circular;
console.log(deepInspect({
name: 'test',
nested: { deep: { value: 42 } },
arr: [1, 2, { x: 3 }],
fn: function test() {},
date: new Date(),
circular
}));
console.log('ā deepInspect works');
// Test diff
console.log('\n--- diff ---');
const obj1 = { a: 1, b: { c: 2 }, d: [1, 2, 3] };
const obj2 = { a: 1, b: { c: 3 }, d: [1, 2], e: 'new' };
const result = diff(obj1, obj2);
console.log(result.summary());
console.log('ā diff works');
// Test traceFunction
console.log('\n--- traceFunction ---');
const add = (a, b) => a + b;
const tracedAdd = traceFunction(add, 'add');
tracedAdd(2, 3);
tracedAdd(5, 7);
console.log('Call count:', tracedAdd.getCallCount());
console.log('ā traceFunction works');
// Test watchProperty
console.log('\n--- watchProperty ---');
const obj = { count: 0 };
const watcher = watchProperty(obj, 'count');
obj.count; // GET
obj.count = 5; // SET
obj.count = 10; // SET
watcher.unwatch();
console.log('ā watchProperty works');
console.log('\n=== Debug Utilities Tests Complete ===\n');
}
*/
// ============================================
// RUN EXERCISES
// ============================================
console.log('=== Debugging Techniques Exercises ===');
console.log('');
console.log('Implement the following exercises:');
console.log('1. DebugLogger - Multi-level namespace logger');
console.log('2. ErrorTracker - Error tracking and grouping');
console.log('3. PerformanceProfiler - Function profiling');
console.log('4. Debug utilities - inspect, diff, trace, watch');
console.log('');
console.log('Uncomment solutions to verify your implementation.');
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
DebugLogger,
ErrorTracker,
PerformanceProfiler,
deepInspect,
diff,
traceFunction,
watchProperty,
};
}