'Best practices for using Sentry SDK in TypeScript and Python.
Scanned 9/2/2026
Install to Claude Code
npx -y skills add jeremylongshore/tons-of-skills-marketplace --skill sentry-sdk-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Sentry Sdk Patterns?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-sentry-sdk-patterns-dcd197ae)More formats (shields.io, HTML) on the badges page.
---
name: sentry-sdk-patterns
description: 'Best practices for using Sentry SDK in TypeScript and Python.
Use when implementing structured error context with scopes, breadcrumb
strategies, beforeSend/beforeBreadcrumb filtering, custom fingerprinting,
user context, or performance span creation.
Trigger: "sentry best practices", "sentry patterns", "sentry sdk usage",
"sentry scope", "sentry breadcrumbs", "sentry beforeSend", "sentry fingerprint".
'
allowed-tools: Read, Write, Edit, Grep
version: 1.51.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- sentry
- python
- typescript
- best-practices
- error-handling
compatibility: Designed for Claude Code
---
# 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`, or `sentry-sdk`)
- `SENTRY_DSN` environment 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:**
```typescript
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:**
```python
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
```typescript
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:**
```python
sentry_sdk.add_breadcrumb(
category="auth", message="User logged in",
level="info", data={"user_id": user_id, "method": "oauth"},
)
```
#### beforeSend -- Drop noise, scrub PII
```typescript
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:**
```python
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
```typescript
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:
```typescript
Sentry.withScope((scope) => {
// Group all payment gateway timeouts together
scope.setFingerprint(['payment-gateway-timeout', gatewayName]);
Sentry.captureException(error);
});
```
```python
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)
```typescript
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
```tsx
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)
```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)
```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
```typescript
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](references/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
- `beforeSend` filter that drops noise and scrubs PII
- `beforeBreadcrumb` callback 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](references/examples.md) for full worked scenarios with Python context managers and async wrappers.
## Resources
- [Sentry JavaScript SDK Best Practices](https://docs.sentry.io/platforms/javascript/best-practices/)
- [Scopes and Context](https://docs.sentry.io/platforms/javascript/enriching-events/scopes/)
- [Express Integration Guide](https://docs.sentry.io/platforms/javascript/guides/express/)
- [Python SDK Documentation](https://docs.sentry.io/platforms/python/)
- [Custom Fingerprinting](https://docs.sentry.io/platforms/javascript/enriching-events/fingerprinting/)
- [Performance Monitoring](https://docs.sentry.io/platforms/javascript/tracing/)
## Next Steps
- **sentry-error-capture** -- Deep dive on `captureException` vs `captureMessage` semantics
- **sentry-performance-tracing** -- Full distributed tracing with `tracesSampleRate` and custom instrumentation
- **sentry-data-handling** -- PII scrubbing, data residency, and GDPR-compliant configuration
- **sentry-common-errors** -- Troubleshooting guide for frequent SDK issues
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!