gs-santry-observability
Sentry Observability (Clean Architecture)
Layer-Specific Logging
| Layer | What to Log | Sentry Feature |
|---|---|---|
| Domain | Entity validation failures | Exception with tags |
| Application | Use Case execution, timing | Transaction spans |
| Infrastructure | Repository operations, DB timing | Child spans |
| Presentation | Action errors, user context | Breadcrumbs + user |
Installation
npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs
Configuration
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
})
Use Case Tracing
Wrap Use Case execution with transaction spans.
// src/backend/application/category/use-cases/create-category.ts
import * as Sentry from '@sentry/nextjs'
export class CreateCategoryUseCase {
constructor(private readonly repository: ICategoryRepository) {}
async execute(input: CreateCategoryInput): Promise<CategoryDTO> {
return Sentry.startSpan(
{
name: 'CreateCategoryUseCase.execute',
op: 'usecase.execute',
attributes: { userId: input.userId },
},
async () => {
// Domain operation
const category = Sentry.startSpan(
{ name: 'Category.create', op: 'domain.entity' },
() => Category.create({ id: crypto.randomUUID(), ...input })
)
// Repository operation
await Sentry.startSpan(
{ name: 'repository.save', op: 'db.write' },
() => this.repository.save(category)
)
return CategoryDTO.fromEntity(category)
}
)
}
}
Repository Timing
Log database operations with timing.
// src/backend/infrastructure/category/repositories/category-repository-impl.ts
import * as Sentry from '@sentry/nextjs'
export class CategoryRepositoryImpl implements ICategoryRepository {
async save(category: Category): Promise<void> {
return Sentry.startSpan(
{
name: 'CategoryRepository.save',
op: 'db.dynamodb',
attributes: { categoryId: category.id },
},
async () => {
await this.model.create(category.toPersistence())
}
)
}
async findById(id: string, userId: string): Promise<Category | null> {
return Sentry.startSpan(
{
name: 'CategoryRepository.findById',
op: 'db.dynamodb',
attributes: { categoryId: id },
},
async () => {
const data = await this.model.get({ pk: userId, sk: id })
return data ? Category.fromPersistence(data) : null
}
)
}
}
Server Action Error Handling
Capture domain exceptions with context.
// src/features/category/actions/create-category.ts
'use server'
import * as Sentry from '@sentry/nextjs'
import { authedProcedure } from '@/lib/procedures'
import { DIContainer, TOKENS } from '@/backend/di'
import { DomainException } from '@/backend/domain/shared/exceptions'
export const createCategoryAction = authedProcedure
.createServerAction()
.input(CreateCategorySchema)
.handler(async ({ input, ctx }) => {
try {
const useCase = DIContainer.resolve(TOKENS.CreateCategoryUseCase)
return await useCase.execute({ ...input, userId: ctx.user.id })
} catch (error) {
if (error instanceof DomainException) {
Sentry.captureException(error, {
tags: {
action: 'createCategory',
layer: 'domain',
code: error.code,
},
contexts: {
input: { name: input.name },
user: { id: ctx.user.id },
},
})
}
throw error
}
})
Procedure with User Context
Set user context early in the procedure chain.
// src/lib/procedures.ts
import * as Sentry from '@sentry/nextjs'
import { createServerActionProcedure } from 'zsa'
export const authedProcedure = createServerActionProcedure()
.handler(async () => {
const session = await auth()
if (!session?.user) {
Sentry.captureMessage('Unauthenticated access attempt', {
level: 'warning',
})
throw new Error('Not authenticated')
}
// Set user context for all subsequent errors
Sentry.setUser({
id: session.user.id,
email: session.user.email,
})
Sentry.addBreadcrumb({
category: 'auth',
message: 'User authenticated',
level: 'info',
})
return { user: session.user }
})
Exception Hierarchy Mapping
Map domain exceptions to Sentry severity levels.
// src/backend/domain/shared/exceptions/sentry-mapper.ts
import * as Sentry from '@sentry/nextjs'
import { DomainException, NotFoundException, ValidationException } from './index'
export function captureToSentry(error: unknown, context: Record<string, unknown>) {
if (error instanceof ValidationException) {
Sentry.captureException(error, {
level: 'warning',
tags: { layer: 'domain', type: 'validation' },
contexts: { validation: context },
})
} else if (error instanceof NotFoundException) {
Sentry.captureException(error, {
level: 'info',
tags: { layer: 'domain', type: 'not_found' },
contexts: { query: context },
})
} else if (error instanceof DomainException) {
Sentry.captureException(error, {
level: 'error',
tags: { layer: 'domain', type: 'business_rule' },
contexts: { domain: context },
})
} else {
Sentry.captureException(error, {
level: 'error',
tags: { layer: 'unknown' },
contexts: { raw: context },
})
}
}
Client Error Boundary
// src/components/error-boundary.tsx
'use client'
import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'
export function ActionErrorBoundary({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
Sentry.captureException(error, {
tags: { component: 'ActionErrorBoundary' },
})
}, [error])
return (
<div>
<h2>Something went wrong</h2>
<button onClick={reset}>Try again</button>
</div>
)
}
Best Practices
- Set user context in procedures - Not in individual actions
- Use spans for Use Cases - Track execution time
- Add breadcrumbs for flow - Auth, validation, processing
- Tag by layer - domain, application, infrastructure, presentation
- Map exceptions to severity - validation=warning, not_found=info, business=error
- Scrub sensitive data - Never log passwords, tokens
Environment Variables
NEXT_PUBLIC_SENTRY_DSN=your-client-dsn
SENTRY_DSN=your-server-dsn
SENTRY_AUTH_TOKEN=your-auth-token
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project
References
- Feature Architecture:
skills/feature-architecture/SKILL.md - Server Actions:
skills/nextjs-server-actions/SKILL.md
More from gilbertopsantosjr/fullstacknextjs
tanstack-react-query
TanStack React Query expert for data fetching and mutations in React applications. Use when working with useQuery, useMutation, cache invalidation, optimistic updates, query keys, or integrating server actions with React Query via @saas4dev/core hooks (useServerActionQuery, useServerActionMutation, useServerActionInfiniteQuery). Triggers on requests involving API data fetching, server state management, cache strategies, or converting fetch/useEffect patterns to React Query.
4gs-feature-architecture
Guide for implementing features in Clean Architecture OOP with Next.js. Use when planning new features, understanding the 4-layer structure (Domain, Application, Infrastructure, Presentation), or deciding where code should live.
3zod-validation
Guide for Zod schema validation patterns in TypeScript. Use when creating validation schemas, defining types, validating forms, API inputs, or handling validation errors.
2gs-sst-infra
Guide for AWS serverless infrastructure using SST v3. Covers DynamoDB, Next.js deployment, Lambda handlers with Clean Architecture adapter pattern, and CI/CD configuration.
2feature-architecture
Guide for implementing features in a layered Next.js full-stack architecture. Use when planning new features, understanding the layer structure (Model, DAL, Service, Actions, Components, Pages), or deciding where code should live.
2nextjs-server-actions
Guide for implementing Next.js server actions using ZSA (Zod Server Actions) with authentication, validation, and React Query integration. Use when creating API endpoints, form handlers, mutations, or any server-side data operations. Best practices for building type-safe Next.js server actions with ZSA (Zod Server Actions). Use this skill when creating validated server actions, implementing authentication/authorization procedures, handling form submissions, integrating with React Query, managing errors, or building secure API-like endpoints in Next.js App Router. Covers input/output validation, procedures (middleware), callbacks, optimistic updates, retries, timeouts, and client-side hooks.
1