sentry-integration

Sentry Integration Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "sentry-integration" with this command: npx skills add blogic-cz/blogic-marketplace/blogic-cz-blogic-marketplace-sentry-integration

Sentry Integration Patterns

Overview

Implement error tracking and performance monitoring using Sentry following the project's established patterns for both client and server.

When to Use This Skill

  • Configuring Sentry SDK

  • Adding error tracking to TRPC procedures

  • Implementing custom spans/traces

  • Setting up user context

  • Working with Sentry API

SDK Configuration

Server-side

// apps/web-app/src/server.ts import * as Sentry from "@sentry/tanstackstart-react";

Sentry.init({ ...(env.SENTRY_DSN && { dsn: env.SENTRY_DSN }), sendDefaultPii: true, environment: env.ENVIRONMENT, release: env.VERSION, dist: "server", spotlight: isDev, // Local dev debugging enableLogs: true, tracesSampleRate: isDev ? 1.0 : 0.001, profilesSampleRate: isDev ? 1.0 : 0.001, profileLifecycle: "trace", integrations: [Sentry.postgresIntegration(), Sentry.redisIntegration(), Sentry.httpIntegration()], ignoreTransactions: ["/api/alive", "/api/health"], beforeSend(event, hint) { // Filter AbortError completely if (error instanceof DOMException && error.name === "AbortError") { return null; } // Mark expected TRPC errors as handled return markExpectedTRPCErrorInEvent(event, error); }, });

Client-side

// apps/web-app/src/client.tsx Sentry.init({ ...(env.VITE_SENTRY_DSN && { dsn: env.VITE_SENTRY_DSN }), sendDefaultPii: true, environment: env.VITE_ENVIRONMENT, release: env.VITE_VERSION, dist: "client", spotlight: isDev, integrations: [ Sentry.tanstackRouterBrowserTracingIntegration(router), Sentry.replayIntegration(), Sentry.feedbackIntegration({ colorScheme: "system", autoInject: false, }), ...(isDev ? [Sentry.spotlightBrowserIntegration()] : []), ], tracesSampleRate: isDev ? 1.0 : 0.001, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, beforeSend(event, hint) { return markExpectedTRPCErrorInEvent(event, hint.originalException); }, });

TRPC Middleware

// apps/web-app/src/infrastructure/trpc/init.ts export const sentryMiddleware = t.middleware( Sentry.trpcMiddleware({ attachRpcInput: true, }), );

// Used in procedure chain export const publicProcedure = t.procedure.use(debugMiddleware).use(sentryMiddleware);

Server Function Middleware

// apps/web-app/src/infrastructure/middleware/sentry-function-middleware.ts export const sentryFunctionMiddleware = createMiddleware({ type: "function", }) .client(async ({ next, serverFnMeta }) => { // Client-side timing and error logging }) .server(async ({ next, data, serverFnMeta }) => { return Sentry.startSpan( { op: "function.server", name: functionName, attributes: { hasData: data !== undefined, functionName, }, }, async (span) => { try { const result = await next(); span.setStatus({ code: 1 }); // OK return result; } catch (error) { captureException(error); span.setStatus({ code: 2 }); // ERROR throw error; } }, ); });

Error Capture Patterns

Expected TRPC Error Codes

// From apps/web-app/src/infrastructure/errors.ts const EXPECTED_TRPC_CODES = ["NOT_FOUND", "FORBIDDEN", "UNAUTHORIZED", "BAD_REQUEST"];

Custom Capture Helper

// apps/web-app/src/infrastructure/sentry-utils.ts export function captureException(error: unknown, captureContext?: Sentry.CaptureContext) { if (isExpectedTRPCError(error)) { Sentry.withScope((scope) => { scope.setLevel("warning"); scope.setTag("error.expected", "true"); Sentry.captureException(error, { mechanism: { type: "generic", handled: true }, }); }); } else { Sentry.captureException(error, captureContext); } }

beforeSend Helper

export function markExpectedTRPCErrorInEvent(event: Sentry.ErrorEvent, error: unknown) { if (!isExpectedTRPCError(error)) return event;

event.exception?.values?.forEach((exception) => { exception.mechanism = { type: "generic", handled: true, }; }); event.level = "warning"; event.tags = { ...event.tags, "error.expected": "true" }; return event; }

Database Query Tracing

Manual Tracing

// apps/web-app/src/infrastructure/db/tracing.ts export async function traced<T>(query: DrizzleQuery, operationName?: string): Promise<T> { return Sentry.startSpan( { op: "db.query", name: sqlString, attributes: { "db.system": "postgresql" }, }, () => query as Promise<T>, ); }

// Usage: const users = await traced(db.select().from(usersTable).where(eq(usersTable.id, userId)));

Automatic Proxy Tracing

// apps/web-app/src/infrastructure/db/traced-db.ts export function createTracedDb(db: Db): Db { return new Proxy(db, { get(target, prop) { if (["select", "insert", "update", "delete"].includes(String(prop))) { return function (...args) { const queryBuilder = originalMethod.call(target, ...args); return createTracedQueryBuilder(queryBuilder, String(prop)); }; } return originalMethod; }, }); }

User Context Hook

// apps/web-app/src/hooks/use-set-sentry-context.ts export function useSetSentryContext() { const { session } = Route.useRouteContext();

useLayoutEffect(() => { if (session?.user) { Sentry.setUser({ id: session.user.id, email: session.user.email, username: session.user.name, }); } else { Sentry.setUser(null); } }, [session]); }

Sentry API Service (Effect-based)

Error Types

// packages/services/src/sentry/errors.ts export class SentryApiError extends Schema.TaggedError<SentryApiError>()("SentryApiError", { statusCode: Schema.Number, body: Schema.String, url: Schema.String, }) {}

export class SentryRateLimitError extends Schema.TaggedError<SentryRateLimitError>()( "SentryRateLimitError", { retryAfter: Schema.Number, message: Schema.String, }, ) {}

Service Pattern

// packages/services/src/sentry/sentry-issues.ts export class SentryIssuesService extends Context.Tag("@project/SentryIssuesService")< SentryIssuesService, { readonly getNewErrors: (params) => Effect.Effect<SentryIssuesResponse, SentryError>; readonly getRegressions: (params) => Effect.Effect<SentryIssuesResponse, SentryError>; readonly verifyToken: (params) => Effect.Effect<SentryOrganization[], SentryError>; readonly listProjects: (params) => Effect.Effect<SentryProject[], SentryError>; }

() { static readonly layer = Layer.effect( SentryIssuesService, Effect.gen(function* () { const httpClient = yield* HttpClient.HttpClient; // ... service implementation }), ); }

// Live layer export const SentryIssuesServiceLive = SentryIssuesService.layer.pipe( Layer.provide(FetchHttpClient.layer), );

API Client with Retry

const retryPolicy = Schedule.exponential("500 millis").pipe(Schedule.compose(Schedule.recurs(2)));

export const sentryRequest = <A, I>( httpClient: HttpClient.HttpClient, schema: Schema.Schema<A, I>, params: SentryRequestParams, ): Effect.Effect<{ data: A; nextCursor: string | null }, SentryApiError | SentryRateLimitError> => Effect.fn("Sentry.request")(function* () { // Request with Bearer token auth // Rate limit handling (429 -> SentryRateLimitError) // Schema validation // Link header pagination parsing })();

Key Rules

  • Use beforeSend to mark expected errors as handled

  • Filter health check endpoints from transactions

  • Set user context in layout effect for session changes

  • Use startSpan for custom instrumentation

  • Handle rate limits (429) with SentryRateLimitError

  • Different sample rates for dev vs production

  • Include replays for error sessions (100% on error)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

marketing-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

debugging-with-opensrc

No summary provided by upstream source.

Repository SourceNeeds Review
General

testing-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

update-packages

No summary provided by upstream source.

Repository SourceNeeds Review