Sentry SDK Patterns
Overview
Production patterns for @sentry/node (v8+) and sentry-sdk (Python 2.x+) covering scoped error context, breadcrumb strategies, event filtering with beforeSend, custom fingerprinting for issue grouping, and performance instrumentation with spans. All examples use real Sentry SDK APIs.
Prerequisites
- Sentry SDK v8+ installed (
@sentry/node,@sentry/react, orsentry-sdk) SENTRY_DSNenvironment variable configured- Familiarity with async/await (TypeScript) or context managers (Python)
Instructions
Step 1 -- Structured Error Context with Scopes
Use Sentry.withScope() (TypeScript) or sentry_sdk.new_scope() (Python) to attach context to individual events without leaking state across requests.
TypeScript -- Scoped error capture:
import * as Sentry from '@sentry/node';
type ErrorSeverity = 'low' | 'medium' | 'high' | 'critical';
interface ErrorOptions {
severity?: ErrorSeverity;
tags?: Record<string, string>;
context?: Record<string, unknown>;
user?: { id: string; email?: string };
fingerprint?: string[];
}
const SEVERITY_MAP: Record<ErrorSeverity, Sentry.SeverityLevel> = {
low: 'info',
medium: 'warning',
high: 'error',
critical: 'fatal',
};
export function captureError(error: Error, options: ErrorOptions = {}) {
Sentry.withScope((scope) => {
scope.setLevel(SEVERITY_MAP[options.severity || 'medium']);
if (options.tags) {
Object.entries(options.tags).forEach(([key, value]) => {
scope.setTag(key, value);
});
}
if (options.context) {
scope.setContext('app', options.context);
}
if (options.user) {
scope.setUser(options.user);
}
if (options.fingerprint) {
scope.setFingerprint(options.fingerprint);
}
Sentry.captureException(error);
});
}
Python -- Scoped error capture:
import sentry_sdk
def capture_error(error, severity="error", tags=None, context=None, user=None):
"""Capture exception with isolated scope context."""
with sentry_sdk.new_scope() as scope:
scope.set_level(severity)
if tags:
for key, value in tags.items():
scope.set_tag(key, value)
if context:
scope.set_context("app", context)
if user:
scope.set_user(user)
sentry_sdk.capture_exception(error)
Key rule: Never call Sentry.setTag() or sentry_sdk.set_tag() at the module level inside request handlers. Those mutate the global scope and leak between concurrent requests. Always use withScope() or new_scope().
Step 2 -- Breadcrumbs, Filtering, and Fingerprints
Structured breadcrumb helpers
import * as Sentry from '@sentry/node';
export const breadcrumb = {
auth(action: string, userId?: string) {
Sentry.addBreadcrumb({
category: 'auth',
message: `${action}${userId ? ` for user ${userId}` : ''}`,
level: 'info',
});
},
db(operation: string, table: string, durationMs?: number) {
Sentry.addBreadcrumb({
category: 'db',
message: `${operation} on ${table}`,
level: 'info',
data: { table, operation, ...(durationMs && { duration_ms: durationMs }) },
});
},
http(method: string, url: string, status: number) {
Sentry.addBreadcrumb({
category: 'http',
message: `${method} ${url} -> ${status}`,
level: status >= 400 ? 'warning' : 'info',
data: { method, url, status_code: status },
});
},
};
Python breadcrumbs:
sentry_sdk.add_breadcrumb(
category="auth", message="User logged in",
level="info", data={"user_id": user_id, "method": "oauth"},
)
beforeSend -- Drop noise, scrub PII
Sentry.init({
dsn: process.env.SENTRY_DSN,
beforeSend(event, hint) {
const error = hint?.originalException;
// Drop non-actionable errors
if (error instanceof Error) {
if (error.message.includes('ResizeObserver loop')) return null;
if (error.message.includes('Network request failed')) return null;
}
// Scrub PII from user context
if (event.user) {
delete event.user.ip_address;
delete event.user.email;
}
return event;
},
});
Python beforeSend:
def before_send(event, hint):
if "exc_info" in hint:
exc_type, exc_value, tb = hint["exc_info"]
if isinstance(exc_value, (KeyboardInterrupt, SystemExit)):
return None
if "user" in event:
event["user"].pop("email", None)
event["user"].pop("ip_address", None)
return event
sentry_sdk.init(dsn=os.environ["SENTRY_DSN"], before_send=before_send)
beforeBreadcrumb -- Filter noisy breadcrumbs
Sentry.init({
dsn: process.env.SENTRY_DSN,
beforeBreadcrumb(breadcrumb, hint) {
// Drop console.log breadcrumbs in production
if (breadcrumb.category === 'console' && breadcrumb.level === 'log') {
return null;
}
// Redact auth tokens from HTTP breadcrumbs
if (breadcrumb.category === 'fetch' && breadcrumb.data?.url) {
const url = new URL(breadcrumb.data.url);
url.searchParams.delete('token');
breadcrumb.data.url = url.toString();
}
return breadcrumb;
},
});
Custom fingerprints for issue grouping
Override default stack-trace grouping when the same root cause produces different stacks:
Sentry.withScope((scope) => {
// Group all payment gateway timeouts together
scope.setFingerprint(['payment-gateway-timeout', gatewayName]);
Sentry.captureException(error);
});
with sentry_sdk.new_scope() as scope:
scope.fingerprint = ["payment-gateway-timeout", gateway_name]
sentry_sdk.capture_exception(error)
Step 3 -- Framework Integration and Performance Spans
Express middleware (Sentry v8)
import * as Sentry from '@sentry/node';
import express from 'express';
const app = express();
// Sentry v8: register error handler
Sentry.setupExpressErrorHandler(app);
// Request context middleware (register BEFORE routes)
app.use((req, res, next) => {
Sentry.setUser({ id: req.user?.id, ip_address: req.ip });
Sentry.addBreadcrumb({
category: 'http',
message: `${req.method} ${req.path}`,
data: { query: req.query, params: req.params },
});
next();
});
React Error Boundary
import * as Sentry from '@sentry/react';
const SentryErrorBoundary = Sentry.withErrorBoundary(App, {
fallback: ({ error, resetError }) => (
<div>
<h2>Something went wrong</h2>
<button onClick={resetError}>Try again</button>
</div>
),
beforeCapture: (scope) => {
scope.setTag('location', 'error-boundary');
scope.setLevel('fatal');
},
});
Performance spans (TypeScript)
async function processOrder(orderId: string) {
return Sentry.startSpan(
{ name: 'processOrder', op: 'task', attributes: { orderId } },
async (span) => {
const order = await Sentry.startSpan(
{ name: 'db.getOrder', op: 'db.query' },
() => db.orders.findById(orderId),
);
await Sentry.startSpan(
{ name: 'payment.charge', op: 'http.client' },
() => chargePayment(order),
);
span.setStatus({ code: 1, message: 'ok' });
return order;
},
);
}
Performance spans (Python)
import sentry_sdk
from functools import wraps
def sentry_traced(op="function"):
"""Decorator to wrap functions in Sentry spans."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
with sentry_sdk.start_span(op=op, name=func.__name__):
return func(*args, **kwargs)
return wrapper
return decorator
@sentry_traced(op="db.query")
def get_user(user_id: str):
return db.users.find_one({"_id": user_id})
Async batch processing with error isolation
async function processItems(items: Item[]) {
const results = await Promise.allSettled(
items.map((item) =>
Sentry.startSpan({ name: `process.${item.type}`, op: 'task' }, () =>
processItem(item),
),
),
);
const failures = results.filter(
(r): r is PromiseRejectedResult => r.status === 'rejected',
);
if (failures.length > 0) {
Sentry.withScope((scope) => {
scope.setTag('batch_size', String(items.length));
scope.setTag('failure_count', String(failures.length));
Sentry.captureMessage(`${failures.length}/${items.length} items failed`, 'warning');
});
failures.forEach((f) => Sentry.captureException(f.reason));
}
}
See implementation.md for Django middleware, test mocking patterns, and additional framework examples.
Output
After applying these patterns you will have:
- Centralized error handler module with typed severity and scoped context
- Structured breadcrumb helpers for auth, db, and http events
beforeSendfilter that drops noise and scrubs PIIbeforeBreadcrumbcallback that redacts sensitive query parameters- Custom fingerprinting for accurate issue grouping
- Framework error boundaries for Express and React
- Performance spans for tracing critical code paths
Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Scope leaking between requests | Global scope mutations in async handlers | Use withScope() / new_scope() for per-event context |
| Duplicate events | Error caught and re-thrown at two layers | Capture at one level only -- middleware or handler, not both |
| Missing breadcrumbs | Cleared after max count (default 100) | Set maxBreadcrumbs in Sentry.init() |
| beforeSend returns undefined | Missing return statement | Always return event or null explicitly |
| Events grouped incorrectly | Default stack-trace fingerprinting | Use scope.setFingerprint() with semantic keys |
| Sentry is not defined | SDK not imported | Verify import * as Sentry from '@sentry/node' |
| Spans not appearing | Missing tracing config | Set tracesSampleRate in Sentry.init() |
Examples
Centralized error handler: Create lib/error-handler.ts wrapping Sentry.withScope() with typed severity, tags, context, user, and fingerprint support.
Breadcrumb trail for checkout: Add breadcrumb.auth('login'), breadcrumb.db('SELECT', 'orders'), breadcrumb.http('POST', '/api/payments', 201) before critical operations so errors include full context timeline.
Noise filtering: Configure beforeSend to drop ResizeObserver loop and Network request failed, scrub PII from user context and cookies.
Fix issue grouping: Add scope.setFingerprint(['payment-gateway-timeout', gatewayName]) to group all payment timeouts by gateway.
See examples.md for full worked scenarios with Python context managers and async wrappers.
Resources
- Sentry JavaScript SDK Best Practices
- Scopes and Context
- Express Integration Guide
- Python SDK Documentation
- Custom Fingerprinting
- Performance Monitoring
Next Steps
- sentry-error-capture -- Deep dive on
captureExceptionvscaptureMessagesemantics - sentry-performance-tracing -- Full distributed tracing with
tracesSampleRateand custom instrumentation - sentry-data-handling -- PII scrubbing, data residency, and GDPR-compliant configuration
- sentry-common-errors -- Troubleshooting guide for frequent SDK issues