Agent Skills: Sentry SDK Patterns

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/sentry-sdk-patterns

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/sentry-pack/skills/sentry-sdk-patterns

Skill Files

Browse the full folder contents for sentry-sdk-patterns.

Download Skill

Loading file tree…

plugins/saas-packs/sentry-pack/skills/sentry-sdk-patterns/SKILL.md

Skill Metadata

Name
sentry-sdk-patterns
Description
|

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:

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
  • 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 for full worked scenarios with Python context managers and async wrappers.

Resources

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