home / skills / gilbertopsantosjr / fullstacknextjs / gs-santry-observability

gs-santry-observability skill

/skills/gs-santry-observability

This skill enhances Sentry observability across Clean Architecture layers by tracing use cases, logging per layer, and capturing user context.

npx playbooks add skill gilbertopsantosjr/fullstacknextjs --skill gs-santry-observability

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.1 KB
---
name: gs-santry-observability
description: Sentry observability for Clean Architecture layers. Error tracking per layer, transaction tracing for Use Cases, user context in procedures, and performance monitoring.
---

# 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

```bash
npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs
```

## Configuration

```typescript
// 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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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

```typescript
// 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

1. **Set user context in procedures** - Not in individual actions
2. **Use spans for Use Cases** - Track execution time
3. **Add breadcrumbs for flow** - Auth, validation, processing
4. **Tag by layer** - domain, application, infrastructure, presentation
5. **Map exceptions to severity** - validation=warning, not_found=info, business=error
6. **Scrub sensitive data** - Never log passwords, tokens

## Environment Variables

```bash
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`

Overview

This skill adds Sentry observability tailored to Clean Architecture layers. It provides layer-specific error tracking, transaction and span tracing for Use Cases and repositories, and user context handling for actionable error reports. The goal is clear telemetry for domain, application, infrastructure, and presentation concerns.

How this skill works

Initialize Sentry with environment-aware sampling and global configuration. Wrap Use Case execution in transaction spans, create child spans for domain entity operations and repository calls, and set user context early in procedure chains so subsequent errors carry user metadata. Capture domain exceptions with tags, contexts, and mapped severity levels, and record breadcrumbs at key flow points.

When to use it

  • You want per-layer error insights (domain, application, infrastructure, presentation).
  • You need timing and performance traces for Use Cases and database ops.
  • You want user-aware error reports for server actions and procedures.
  • You need consistent exception severity mapping and contextual tags.
  • You want breadcrumbs to follow user flow and authentication events.

Best practices

  • Set Sentry user context once in the authenticated procedure, not in each action.
  • Wrap Use Case.execute with a transaction span and add child spans for domain and repository work.
  • Tag errors by layer and include minimal context objects (avoid sensitive fields).
  • Map domain exception types to severity levels: validation=warning, not_found=info, business=error.
  • Add breadcrumbs for auth, validation, and key processing steps to reconstruct flow.

Example use cases

  • Trace execution time of a CreateCategory Use Case and surface slow repository writes.
  • Capture a ValidationException with tags and input context when entity creation fails.
  • Set user context during authentication so server action errors include user id and email.
  • Record DB operation spans for CategoryRepository.save and CategoryRepository.findById to monitor latency.
  • Report client-side action errors from an ErrorBoundary with component tags and automatic capture.

FAQ

How do I avoid logging sensitive data?

Scrub or omit passwords, tokens, and PII from contexts and breadcrumbs before sending them to Sentry.

Where should I set the Sentry DSN and sampling rate?

Configure DSN and tracesSampleRate in server-side Sentry.init using environment variables and reduce sampling in production.