Capturing Errors
Learn how to capture errors automatically and manually in your applications.
Overview
There are two ways to capture errors:
- Automatic Capture - SDK captures uncaught exceptions and unhandled rejections
- Manual Capture - You explicitly capture errors in your code
Automatic Capture
Enable automatic error capture during initialization:
import { initObservability } from '@transactional/observability';
initObservability({
dsn: 'your-dsn',
enableErrorTracking: true,
autoCapture: {
// Capture uncaught exceptions (default: true)
uncaughtExceptions: true,
// Capture unhandled promise rejections (default: true)
unhandledRejections: true,
// Capture console.error calls (default: false)
consoleErrors: false,
},
});What Gets Captured Automatically
| Event | Captured By Default |
|---|---|
throw new Error() (uncaught) | Yes |
| Unhandled Promise rejection | Yes |
window.onerror (browser) | Yes |
process.on('uncaughtException') (Node) | Yes |
console.error() | No (opt-in) |
Manual Capture
captureException()
Capture caught exceptions:
import { getObservability } from '@transactional/observability';
const obs = getObservability();
try {
await riskyOperation();
} catch (error) {
// Capture the error
obs.captureException(error as Error);
// Handle gracefully
showErrorToUser();
}With Additional Context
Add context to help with debugging:
obs.captureException(error as Error, {
// Custom tags for filtering
tags: {
feature: 'checkout',
paymentProvider: 'stripe',
},
// Extra debugging data
extra: {
orderId: order.id,
cartItems: cart.items.length,
totalAmount: cart.total,
},
// User who experienced the error
user: {
id: user.id,
email: user.email,
},
// Override severity
severity: 'warning',
// Custom fingerprint for grouping
fingerprint: ['checkout-error', error.code],
});captureMessage()
Capture non-exception error messages:
// Simple message
obs.captureMessage('User attempted checkout with empty cart');
// With severity
obs.captureMessage('Payment retry succeeded after 3 attempts', 'warning');
// With full context
obs.captureMessage('Rate limit exceeded for user', 'warning', {
tags: { endpoint: '/api/generate' },
extra: { userId: user.id, requestsToday: 150 },
});React Error Boundaries
Capture errors in React component trees:
Using the Built-in ErrorBoundary
import { ErrorBoundary } from '@transactional/observability/react';
function App() {
return (
<ErrorBoundary
fallback={<ErrorPage />}
onError={(error, componentStack) => {
console.error('React error:', error);
}}
>
<YourApp />
</ErrorBoundary>
);
}Custom Error Boundary
import { Component, ReactNode } from 'react';
import { getObservability } from '@transactional/observability';
interface Props {
children: ReactNode;
fallback: ReactNode;
}
interface State {
hasError: boolean;
}
class CustomErrorBoundary extends Component<Props, State> {
state = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: { componentStack: string }) {
const obs = getObservability();
obs.captureException(error, {
extra: {
componentStack: errorInfo.componentStack,
},
tags: {
errorBoundary: 'custom',
},
});
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}Granular Error Boundaries
Wrap specific features for better isolation:
function Dashboard() {
return (
<div>
<Header />
<ErrorBoundary fallback={<ChartError />}>
<AnalyticsChart />
</ErrorBoundary>
<ErrorBoundary fallback={<TableError />}>
<DataTable />
</ErrorBoundary>
</div>
);
}Express/Hono Middleware
Express
import express from 'express';
import { getObservability } from '@transactional/observability';
const app = express();
// Your routes
app.get('/api/users', async (req, res) => {
// ...
});
// Error handling middleware (must be last)
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
const obs = getObservability();
obs.captureException(err, {
tags: {
route: req.path,
method: req.method,
},
extra: {
query: req.query,
body: req.body,
},
user: req.user ? { id: req.user.id } : undefined,
});
res.status(500).json({ error: 'Internal Server Error' });
});Hono
import { Hono } from 'hono';
import { getObservability } from '@transactional/observability';
const app = new Hono();
// Error handler
app.onError((err, c) => {
const obs = getObservability();
obs.captureException(err, {
tags: {
route: c.req.path,
method: c.req.method,
},
request: {
url: c.req.url,
method: c.req.method,
headers: Object.fromEntries(c.req.raw.headers),
},
});
return c.json({ error: 'Internal Server Error' }, 500);
});Next.js Integration
App Router - Error Component
// app/error.tsx
'use client';
import { useEffect } from 'react';
import { getObservability } from '@transactional/observability';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
const obs = getObservability();
obs.captureException(error, {
tags: { nextjs: 'app-router', boundary: 'error.tsx' },
extra: { digest: error.digest },
});
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}App Router - Global Error
// app/global-error.tsx
'use client';
import { useEffect } from 'react';
import { getObservability } from '@transactional/observability';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
const obs = getObservability();
obs.captureException(error, {
tags: { nextjs: 'app-router', boundary: 'global-error.tsx' },
severity: 'fatal',
});
}, [error]);
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</body>
</html>
);
}API Routes
// app/api/users/route.ts
import { NextResponse } from 'next/server';
import { getObservability } from '@transactional/observability';
export async function GET(request: Request) {
const obs = getObservability();
try {
const users = await fetchUsers();
return NextResponse.json(users);
} catch (error) {
obs.captureException(error as Error, {
tags: { route: '/api/users', method: 'GET' },
});
return NextResponse.json(
{ error: 'Failed to fetch users' },
{ status: 500 }
);
}
}Async Error Handling
Async/Await
async function processOrder(orderId: string) {
const obs = getObservability();
try {
const order = await fetchOrder(orderId);
await chargePayment(order);
await fulfillOrder(order);
} catch (error) {
obs.captureException(error as Error, {
tags: { operation: 'process-order' },
extra: { orderId },
});
throw error; // Re-throw if needed
}
}Promise Chains
fetchOrder(orderId)
.then(chargePayment)
.then(fulfillOrder)
.catch((error) => {
obs.captureException(error, {
tags: { operation: 'process-order' },
extra: { orderId },
});
});Filtering Errors
Before Send Hook
Filter or modify errors before they're sent:
initObservability({
dsn: 'your-dsn',
enableErrorTracking: true,
beforeSend: (event) => {
// Don't send errors from development
if (event.environment === 'development') {
return null;
}
// Don't send network errors
if (event.exceptionType === 'NetworkError') {
return null;
}
// Scrub sensitive data
if (event.request?.headers) {
delete event.request.headers['authorization'];
}
return event;
},
});Ignore Errors
Ignore specific error types:
initObservability({
dsn: 'your-dsn',
enableErrorTracking: true,
ignoreErrors: [
// Ignore by message pattern
/ResizeObserver loop/,
/Network request failed/,
// Ignore by exception type
'AbortError',
'CancelledError',
],
});Sampling
Control how many errors are captured:
initObservability({
dsn: 'your-dsn',
enableErrorTracking: true,
// Capture 50% of errors (for high-volume apps)
sampleRate: 0.5,
// Or use a function for dynamic sampling
tracesSampler: (context) => {
// Always capture fatal errors
if (context.severity === 'fatal') {
return 1.0;
}
// Sample 10% of warnings
if (context.severity === 'warning') {
return 0.1;
}
// Sample 50% of regular errors
return 0.5;
},
});Best Practices
1. Capture Early, Handle Gracefully
try {
await riskyOperation();
} catch (error) {
// Capture first
obs.captureException(error as Error);
// Then handle gracefully
return fallbackValue;
}2. Add Meaningful Context
obs.captureException(error, {
tags: {
feature: 'payment',
provider: 'stripe',
},
extra: {
orderId: order.id,
amount: order.total,
currency: order.currency,
},
});3. Don't Over-Capture
// Bad - captures expected validation errors
try {
validateInput(data);
} catch (error) {
obs.captureException(error); // Noise!
showValidationError(error);
}
// Good - only capture unexpected errors
try {
await processData(validatedData);
} catch (error) {
obs.captureException(error);
showGenericError();
}4. Use Appropriate Severity
// Fatal - app can't continue
obs.captureMessage('Database connection lost', 'fatal');
// Error - operation failed
obs.captureException(paymentError, { severity: 'error' });
// Warning - degraded but functional
obs.captureMessage('Cache miss, using database', 'warning');Next Steps
- Stack Traces - Configure stack trace capture
- Breadcrumbs - Track user actions
- Context - Add rich debugging context
- Alerts - Set up error notifications
On This Page
- Overview
- Automatic Capture
- What Gets Captured Automatically
- Manual Capture
- captureException()
- With Additional Context
- captureMessage()
- React Error Boundaries
- Using the Built-in ErrorBoundary
- Custom Error Boundary
- Granular Error Boundaries
- Express/Hono Middleware
- Express
- Hono
- Next.js Integration
- App Router - Error Component
- App Router - Global Error
- API Routes
- Async Error Handling
- Async/Await
- Promise Chains
- Filtering Errors
- Before Send Hook
- Ignore Errors
- Sampling
- Best Practices
- 1. Capture Early, Handle Gracefully
- 2. Add Meaningful Context
- 3. Don't Over-Capture
- 4. Use Appropriate Severity
- Next Steps