Back to patterns
Stack Trace Analyzer
RUNTIME
A structured approach to runtime error debugging, helping developers navigate complex stack traces, identify error sources, and implement error boundaries.
Use Cases
- Error tracking
- Stack trace analysis
- Error boundaries
- Runtime debugging
Production Implementation
Implementation
// Real-world example: Error tracking and analysis system
interface StackFrame {
fileName: string;
lineNumber: number;
columnNumber: number;
functionName: string;
source?: string;
}
interface ErrorReport {
id: string;
timestamp: Date;
error: {
name: string;
message: string;
stack: StackFrame[];
};
context: {
url: string;
userAgent: string;
[key: string]: any;
};
}
class ErrorTracker {
private static instance: ErrorTracker;
private reports: ErrorReport[] = [];
private contextProviders: Map<string, () => any> = new Map();
private errorListeners: Set<(report: ErrorReport) => void> = new Set();
private logger: Console;
private constructor() {
this.logger = console;
this.setupGlobalHandlers();
}
static getInstance(): ErrorTracker {
if (!ErrorTracker.instance) {
ErrorTracker.instance = new ErrorTracker();
}
return ErrorTracker.instance;
}
private setupGlobalHandlers(): void {
// Handle uncaught errors
window.addEventListener('error', (event) => {
this.handleError(event.error);
});
// Handle unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.handleError(event.reason);
});
// Override console.error
const originalError = console.error;
console.error = (...args) => {
const error = args[0];
if (error instanceof Error) {
this.handleError(error);
}
originalError.apply(console, args);
};
}
addContextProvider(name: string, provider: () => any): void {
this.contextProviders.set(name, provider);
}
addEventListener(listener: (report: ErrorReport) => void): () => void {
this.errorListeners.add(listener);
return () => this.errorListeners.delete(listener);
}
async handleError(error: Error): Promise<void> {
try {
const stackFrames = this.parseStackTrace(error);
const context = this.gatherContext();
const report: ErrorReport = {
id: this.generateErrorId(),
timestamp: new Date(),
error: {
name: error.name,
message: error.message,
stack: stackFrames
},
context
};
this.reports.push(report);
this.notifyListeners(report);
await this.sendToServer(report);
this.logger.debug('Error report generated:', report);
} catch (e) {
this.logger.error('Failed to handle error:', e);
}
}
private parseStackTrace(error: Error): StackFrame[] {
const stackLines = error.stack?.split('\n') || [];
const frames: StackFrame[] = [];
for (const line of stackLines) {
const frame = this.parseStackFrame(line);
if (frame) frames.push(frame);
}
return frames;
}
private parseStackFrame(line: string): StackFrame | null {
// Chrome-style stack frame
const chromeMatch = line.match(
/at (?:(.+?)s+()?(?:(.+?):(d+):(d+)))?/
);
if (chromeMatch) {
const [, fnName, fileName, lineNo, colNo] = chromeMatch;
return {
functionName: fnName || '<anonymous>',
fileName,
lineNumber: parseInt(lineNo, 10),
columnNumber: parseInt(colNo, 10),
source: line.trim()
};
}
// Firefox-style stack frame
const firefoxMatch = line.match(
/(.*)@(.+):(d+):(d+)/
);
if (firefoxMatch) {
const [, fnName, fileName, lineNo, colNo] = firefoxMatch;
return {
functionName: fnName || '<anonymous>',
fileName,
lineNumber: parseInt(lineNo, 10),
columnNumber: parseInt(colNo, 10),
source: line.trim()
};
}
return null;
}
private gatherContext(): any {
const context: any = {
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
};
// Add custom context from providers
for (const [name, provider] of this.contextProviders) {
try {
context[name] = provider();
} catch (e) {
this.logger.warn(`Context provider "${name}" failed:`, e);
}
}
return context;
}
private generateErrorId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
private notifyListeners(report: ErrorReport): void {
for (const listener of this.errorListeners) {
try {
listener(report);
} catch (e) {
this.logger.error('Error listener failed:', e);
}
}
}
private async sendToServer(report: ErrorReport): Promise<void> {
try {
const response = await fetch('/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(report)
});
if (!response.ok) {
throw new Error(`Server responded with ${response.status}`);
}
} catch (e) {
this.logger.error('Failed to send error report:', e);
}
}
getErrorSummary(): {
total: number;
recent: ErrorReport[];
topErrors: Array<{
name: string;
count: number;
}>;
} {
const recent = this.reports
.slice(-10)
.reverse();
const errorCounts = this.reports.reduce((acc, report) => {
const name = report.error.name;
acc[name] = (acc[name] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const topErrors = Object.entries(errorCounts)
.map(([name, count]) => ({ name, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 5);
return {
total: this.reports.length,
recent,
topErrors
};
}
}
// React Error Boundary implementation
import React from 'react';
interface ErrorBoundaryProps {
fallback: React.ReactNode;
onError?: (error: Error, info: React.ErrorInfo) => void;
children: React.ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
}
class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: React.ErrorInfo): void {
const { onError } = this.props;
// Track error
ErrorTracker.getInstance().handleError(error);
// Call custom error handler
if (onError) {
onError(error, info);
}
}
render(): React.ReactNode {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
// Example usage
// Initialize error tracker
const errorTracker = ErrorTracker.getInstance();
// Add context providers
errorTracker.addContextProvider('user', () => ({
id: getCurrentUser()?.id,
role: getCurrentUser()?.role
}));
errorTracker.addContextProvider('app', () => ({
version: process.env.VERSION,
environment: process.env.NODE_ENV
}));
// Add error listener
errorTracker.addEventListener((report) => {
// Send to monitoring service
monitoring.trackError(report);
});
// Use in React application
function App() {
return (
<ErrorBoundary
fallback={<ErrorPage />}
onError={(error, info) => {
// Custom error handling
notifySupport(error);
}}
>
<MainContent />
</ErrorBoundary>
);
}
// Example error handling in async function
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return await response.json();
} catch (error) {
errorTracker.handleError(error);
throw error;
}
}
Code Examples
Common Error Handling Issues
typescript
// ❌ Common error handling issues
class Service {
// Issue 1: Generic error handling
async fetchData() {
try {
const response = await fetch('/api/data');
return await response.json();
} catch (error) {
console.error('Error:', error);
}
}
// Issue 2: Swallowing errors
processItem(item: any) {
try {
return this.transform(item);
} catch {
return null;
}
}
// Issue 3: No error boundaries
render() {
return <Component />;
}
// Issue 4: Poor error information
async saveData(data: any) {
if (!this.validate(data)) {
throw new Error('Invalid data');
}
}
}
Common issues include generic error handling, swallowing errors, missing error boundaries, and poor error information.
Robust Error Handling
typescript
// ✅ Robust error handling
class Service {
// Specific error types
class APIError extends Error {
constructor(
message: string,
public status: number,
public code: string
) {
super(message);
this.name = 'APIError';
}
}
// Proper error handling with context
async fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new APIError(
'API request failed',
response.status,
await response.text()
);
}
return await response.json();
} catch (error) {
// Track error with context
ErrorTracker.getInstance().handleError(error, {
endpoint: '/api/data',
method: 'GET'
});
// Rethrow for boundary handling
throw error;
}
}
// Error boundary implementation
render() {
return (
<ErrorBoundary
fallback={<ErrorDisplay />}
onError={(error) => {
// Log error
logger.error('Component error:', error);
// Show user feedback
notifications.show({
type: 'error',
message: 'Something went wrong'
});
}}
>
<Component />
</ErrorBoundary>
);
}
// Detailed error information
async saveData(data: any) {
const validation = this.validate(data);
if (!validation.valid) {
throw new ValidationError(
'Data validation failed',
validation.errors,
{
fields: validation.failedFields,
data: JSON.stringify(data)
}
);
}
}
// Cleanup on error
async processItems(items: any[]) {
const processed = new Set();
try {
for (const item of items) {
await this.process(item);
processed.add(item.id);
}
} catch (error) {
// Cleanup processed items
await this.rollback(Array.from(processed));
throw error;
}
}
}
This implementation includes specific error types, proper error tracking, error boundaries, and cleanup handling.
Best Practices
- Use specific error types
- Include error context
- Implement error boundaries
- Add error tracking
- Handle async errors
- Provide cleanup mechanisms
- Log error details
Common Pitfalls
- Generic error handling
- Swallowing errors
- Missing error boundaries
- Poor error information
- No error tracking
- Inconsistent error handling
- Missing cleanup on error
Related Patterns
Promise Chaining
Visualize and debug complex asynchronous flows and promise chains for structured reasoning and step-by-step task completion.
Memory Leaks 101
A systematic approach to identifying and fixing memory leaks through heap snapshot analysis, memory usage visualization, and common leak pattern detection.