From sentry-pack
Provides Sentry SDK patterns for TypeScript/Node and Python: scoped error context, breadcrumbs, beforeSend filtering, custom fingerprinting, user context, and performance spans.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin sentry-packThis skill is limited to using the following tools:
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.
Implements Sentry error capture and enrichment with exceptions, messages, context, breadcrumbs, and fingerprints for Node.js and Python apps.
Initializes and configures Sentry SDKs with tracing, replays, and integrations for JavaScript/TypeScript (Browser, Node, React, Next.js), Python/Django, and Go.
Sets up full Sentry SDK for Python apps including error monitoring, tracing, profiling, logging, metrics, crons, and AI monitoring. Supports Django, Flask, FastAPI, Celery, Starlette, AIOHTTP, Tornado.
Share bugs, ideas, or general feedback.
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.
@sentry/node, @sentry/react, or sentry-sdk)SENTRY_DSN environment variable configuredUse 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().
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"},
)
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)
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;
},
});
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)
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();
});
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');
},
});
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;
},
);
}
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 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.
After applying these patterns you will have:
beforeSend filter that drops noise and scrubs PIIbeforeBreadcrumb callback that redacts sensitive query parameters| 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() |
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.
captureException vs captureMessage semanticstracesSampleRate and custom instrumentation