Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:46:44 +08:00
commit d518f4f28d
13 changed files with 1653 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "sentry-and-otel-setup",
"description": "This skill should be used when adding error tracking and performance monitoring with Sentry and OpenTelemetry tracing to Next.js applications. Apply when setting up error monitoring, configuring tracing for Server Actions and routes, implementing logging wrappers, adding performance instrumentation, or establishing observability for debugging production issues.",
"version": "1.0.0",
"author": {
"name": "Hope Overture",
"email": "support@worldbuilding-app-skills.dev"
},
"skills": [
"./skills"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# sentry-and-otel-setup
This skill should be used when adding error tracking and performance monitoring with Sentry and OpenTelemetry tracing to Next.js applications. Apply when setting up error monitoring, configuring tracing for Server Actions and routes, implementing logging wrappers, adding performance instrumentation, or establishing observability for debugging production issues.

81
plugin.lock.json Normal file
View File

@@ -0,0 +1,81 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:hopeoverture/worldbuilding-app-skills:plugins/sentry-and-otel-setup",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "bb9d6c73e4d4f9a3524bdebba9d39880dc641b10",
"treeHash": "6bf1280ebbe2ac6fffe2f42927e8d774f40015bbd2a4a2d511c4599dd5eebf3d",
"generatedAt": "2025-11-28T10:17:32.904133Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "sentry-and-otel-setup",
"description": "This skill should be used when adding error tracking and performance monitoring with Sentry and OpenTelemetry tracing to Next.js applications. Apply when setting up error monitoring, configuring tracing for Server Actions and routes, implementing logging wrappers, adding performance instrumentation, or establishing observability for debugging production issues.",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "ec0f2ff53bd491f2609820974de33eb0f29fe8299005e391d43e1e8e55d7a3bc"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "55623282ba58d99393247c3f4e253af1f901ecebf8a7ce03b8cc2ddfb9293cfa"
},
{
"path": "skills/sentry-and-otel-setup/SKILL.md",
"sha256": "e4c7683cc234874369e452452f94d6572a7aee0718fcf7649b9e3aee9b06be3e"
},
{
"path": "skills/sentry-and-otel-setup/references/otel-integration.md",
"sha256": "5502b6a769bedc96243453c6b730dc96d4b7169ab3a77671b935d6ef471dfbfb"
},
{
"path": "skills/sentry-and-otel-setup/references/sentry-best-practices.md",
"sha256": "2b7028e418f335fdba46e72836d717546639e9e98a0536463f8ff6420a6f29e1"
},
{
"path": "skills/sentry-and-otel-setup/assets/error-boundary.tsx",
"sha256": "7cd8d3284d3f40eafad77ba150d8f7fa66f61d050e231b9a6d244e1eafdfc467"
},
{
"path": "skills/sentry-and-otel-setup/assets/sentry-server-config.ts",
"sha256": "77d0f5041e142b4a5cb1dbe6f1786e85ee6a706e959369fe4145fea77b6d8d81"
},
{
"path": "skills/sentry-and-otel-setup/assets/logger.ts",
"sha256": "a823cd25ac212c95c9c8ce7c1e741c2a7397dafcb8f211681ac2594504e8a7cf"
},
{
"path": "skills/sentry-and-otel-setup/assets/sentry-client-config.ts",
"sha256": "0604e7afb0d11a24ff93a70de60ce401ccd864b2868d8a34c903de35ab9ac770"
},
{
"path": "skills/sentry-and-otel-setup/assets/error-page.tsx",
"sha256": "8acfbb7e64fe0a94e452cac4bdae59e9256dd831bdaad979bbcc88810b781f6f"
},
{
"path": "skills/sentry-and-otel-setup/assets/instrumentation.ts",
"sha256": "eaeb2f76eb61c89a8d32aa44bb48a651512c7767d7a45d37d8bcd62bf0ce2d9d"
},
{
"path": "skills/sentry-and-otel-setup/assets/global-error.tsx",
"sha256": "739f7c9064b3ab421c6646e80390d0bd23561458f438cd6e2f22a6b7eacd52c6"
}
],
"dirSha256": "6bf1280ebbe2ac6fffe2f42927e8d774f40015bbd2a4a2d511c4599dd5eebf3d"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,383 @@
---
name: sentry-and-otel-setup
description: This skill should be used when adding error tracking and performance monitoring with Sentry and OpenTelemetry tracing to Next.js applications. Apply when setting up error monitoring, configuring tracing for Server Actions and routes, implementing logging wrappers, adding performance instrumentation, or establishing observability for debugging production issues.
---
# Sentry and OpenTelemetry Setup
## Overview
Configure comprehensive error tracking and performance monitoring using Sentry with OpenTelemetry (OTel) instrumentation for Next.js applications, including automatic error capture, distributed tracing, and custom logging.
## Installation and Configuration
### 1. Install Sentry
Install Sentry Next.js SDK:
```bash
npm install @sentry/nextjs
```
Run Sentry wizard for automatic configuration:
```bash
npx @sentry/wizard@latest -i nextjs
```
This creates:
- `sentry.client.config.ts` - Client-side configuration
- `sentry.server.config.ts` - Server-side configuration
- `sentry.edge.config.ts` - Edge runtime configuration
- `instrumentation.ts` - OpenTelemetry setup
- Updates `next.config.js` with Sentry webpack plugin
### 2. Configure Environment Variables
Add Sentry credentials to `.env.local`:
```env
SENTRY_DSN=https://your-dsn@sentry.io/project-id
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project
NEXT_PUBLIC_SENTRY_DSN=https://your-dsn@sentry.io/project-id
```
Get DSN from Sentry dashboard: Settings > Projects > [Your Project] > Client Keys (DSN)
**For production**, add these to deployment environment variables.
### 3. Update Sentry Configurations
Customize `sentry.server.config.ts` using the template from `assets/sentry-server-config.ts`:
- Set environment (development, staging, production)
- Configure sample rates for performance monitoring
- Enable tracing for Server Actions and API routes
- Set up error filtering and breadcrumbs
Customize `sentry.client.config.ts` using the template from `assets/sentry-client-config.ts`:
- Configure replay sessions for debugging
- Set error boundaries
- Enable performance monitoring for user interactions
### 4. Add Instrumentation Hook
Create or update `instrumentation.ts` in project root using the template from `assets/instrumentation.ts`. This:
- Initializes OpenTelemetry before app starts
- Registers Sentry as trace provider
- Enables distributed tracing across services
- Runs only once on server startup
**Note**: Requires `experimental.instrumentationHook` in `next.config.js` (added by Sentry wizard).
### 5. Create Logging Wrapper
Create `lib/logger.ts` using the template from `assets/logger.ts`. This provides:
- Structured logging with context
- Automatic Sentry integration
- Different log levels (debug, info, warn, error)
- Request context capture
Use instead of `console.log` for better debugging:
```typescript
import { logger } from '@/lib/logger';
logger.info('User logged in', { userId: user.id });
logger.error('Failed to save data', { error, userId });
```
### 6. Add Error Boundary (Client Components)
Create `components/error-boundary.tsx` using the template from `assets/error-boundary.tsx`. This:
- Catches React errors in client components
- Sends errors to Sentry
- Shows fallback UI
- Provides error recovery
Use in layouts or pages:
```typescript
import { ErrorBoundary } from '@/components/error-boundary';
export default function Layout({ children }) {
return (
<ErrorBoundary>
{children}
</ErrorBoundary>
);
}
```
### 7. Create Custom Error Page
Update `app/error.tsx` using the template from `assets/error-page.tsx`. This:
- Shows user-friendly error messages
- Captures errors in Server Components
- Provides retry functionality
- Sends errors to Sentry
### 8. Add Global Error Handler
Update `app/global-error.tsx` using the template from `assets/global-error.tsx`. This:
- Catches errors in root layout
- Last resort error boundary
- Required for catching layout errors
## Tracing Server Actions
### Manual Instrumentation
Wrap Server Actions with Sentry tracing:
```typescript
'use server';
import { logger } from '@/lib/logger';
import * as Sentry from '@sentry/nextjs';
export async function createPost(formData: FormData) {
return await Sentry.startSpan(
{ name: 'createPost', op: 'server.action' },
async () => {
try {
const title = formData.get('title') as string;
logger.info('Creating post', { title });
// Your logic here
const post = await prisma.post.create({
data: { title, content: '...' },
});
logger.info('Post created', { postId: post.id });
return { success: true, post };
} catch (error) {
logger.error('Failed to create post', { error });
Sentry.captureException(error);
throw error;
}
}
);
}
```
### Automatic Instrumentation
Sentry automatically instruments:
- Next.js API routes
- Server Components (partial)
- Fetch requests
- Database queries (with OTel)
## Monitoring Patterns
### 1. Capture User Context
Associate errors with users:
```typescript
import * as Sentry from '@sentry/nextjs';
import { getCurrentUser } from '@/lib/auth/utils';
export async function setUserContext() {
const user = await getCurrentUser();
if (user) {
Sentry.setUser({
id: user.id,
email: user.email,
});
}
}
```
Call in layouts or middleware to track user context globally.
### 2. Add Custom Tags
Tag errors for filtering:
```typescript
Sentry.setTag('feature', 'worldbuilding');
Sentry.setTag('entity_type', 'character');
// Now errors are tagged and filterable in Sentry dashboard
```
### 3. Add Breadcrumbs
Track user actions leading to errors:
```typescript
Sentry.addBreadcrumb({
category: 'user_action',
message: 'User clicked create entity',
level: 'info',
data: {
entityType: 'character',
worldId: 'world-123',
},
});
```
### 4. Performance Monitoring
Track custom operations:
```typescript
import * as Sentry from '@sentry/nextjs';
export async function complexOperation() {
const transaction = Sentry.startTransaction({
name: 'Complex World Generation',
op: 'task',
});
// Step 1
const span1 = transaction.startChild({
op: 'generate.terrain',
description: 'Generate terrain data',
});
await generateTerrain();
span1.finish();
// Step 2
const span2 = transaction.startChild({
op: 'generate.biomes',
description: 'Generate biome data',
});
await generateBiomes();
span2.finish();
transaction.finish();
}
```
### 5. Database Query Tracing
Prisma automatically integrates with OTel:
```typescript
// Queries are automatically traced if OTel is configured
const users = await prisma.user.findMany();
// Shows up in Sentry as a database span
```
## Configuration Options
### Sample Rates
Control how many events are sent to Sentry (avoid quota limits):
```typescript
// sentry.server.config.ts
Sentry.init({
dsn: process.env.SENTRY_DSN,
// Percentage of errors to capture (1.0 = 100%)
sampleRate: 1.0,
// Percentage of transactions to trace
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
// Percentage of sessions to replay
replaysSessionSampleRate: 0.1,
// Percentage of error sessions to replay
replaysOnErrorSampleRate: 1.0,
});
```
### Environment Detection
Configure different settings per environment:
```typescript
Sentry.init({
environment: process.env.NODE_ENV,
enabled: process.env.NODE_ENV !== 'development', // Disable in dev
beforeSend(event, hint) {
// Filter out specific errors
if (event.exception?.values?.[0]?.value?.includes('ResizeObserver')) {
return null; // Don't send to Sentry
}
return event;
},
});
```
### Source Maps
Ensure source maps are uploaded for readable stack traces:
```typescript
// next.config.js (added by Sentry wizard)
const { withSentryConfig } = require('@sentry/nextjs');
module.exports = withSentryConfig(
nextConfig,
{
silent: true,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
},
{
hideSourceMaps: true,
widenClientFileUpload: true,
}
);
```
## Best Practices
1. **Use logger wrapper**: Centralize logging for consistency
2. **Set user context**: Associate errors with users for debugging
3. **Add breadcrumbs**: Track user journey before errors
4. **Monitor performance**: Use tracing for slow operations
5. **Filter noise**: Exclude known non-critical errors
6. **Configure sample rates**: Balance visibility with quota
7. **Test in staging**: Verify Sentry integration before production
8. **Review regularly**: Check Sentry dashboard for patterns
## Troubleshooting
**Sentry not capturing errors**: Check DSN is correct and Sentry is initialized. Verify `instrumentation.ts` exports `register()`.
**Source maps not working**: Ensure auth token is set and source maps are uploaded during build. Check Sentry dashboard > Settings > Source Maps.
**High quota usage**: Reduce sample rates in production. Filter out noisy errors with `beforeSend`.
**Traces not appearing**: Verify `tracesSampleRate` > 0. Check OpenTelemetry is initialized in `instrumentation.ts`.
**Client errors not captured**: Ensure `NEXT_PUBLIC_SENTRY_DSN` is set and accessible from browser.
## Resources
### scripts/
No executable scripts needed for this skill.
### references/
- `sentry-best-practices.md` - Error handling patterns, performance monitoring strategies, and quota management
- `otel-integration.md` - OpenTelemetry concepts, custom instrumentation, and distributed tracing setup
### assets/
- `sentry-server-config.ts` - Server-side Sentry configuration with tracing and sampling
- `sentry-client-config.ts` - Client-side Sentry configuration with replay and error boundaries
- `instrumentation.ts` - OpenTelemetry initialization and Sentry integration
- `logger.ts` - Structured logging wrapper with Sentry integration
- `error-boundary.tsx` - React error boundary component for client-side error handling
- `error-page.tsx` - Custom error page for Server Component errors
- `global-error.tsx` - Global error handler for root layout errors

View File

@@ -0,0 +1,71 @@
'use client';
import * as Sentry from '@sentry/nextjs';
import { Component, type ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log error to Sentry
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack,
},
},
});
}
render() {
if (this.state.hasError) {
// Render custom fallback UI
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="max-w-md rounded-lg border border-red-200 bg-red-50 p-6">
<h2 className="mb-2 text-xl font-semibold text-red-900">
Something went wrong
</h2>
<p className="mb-4 text-red-700">
An error occurred while rendering this component.
</p>
{process.env.NODE_ENV === 'development' && this.state.error && (
<pre className="mb-4 overflow-auto rounded bg-red-100 p-2 text-xs text-red-900">
{this.state.error.message}
</pre>
)}
<button
onClick={() => this.setState({ hasError: false, error: undefined })}
className="rounded bg-red-600 px-4 py-2 text-white hover:bg-red-700"
>
Try again
</button>
</div>
</div>
);
}
return this.props.children;
}
}

View File

@@ -0,0 +1,61 @@
'use client';
import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';
export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log error to Sentry
Sentry.captureException(error);
}, [error]);
return (
<div className="flex min-h-screen items-center justify-center p-4">
<div className="max-w-md rounded-lg border p-6">
<h2 className="mb-2 text-2xl font-semibold">Something went wrong</h2>
<p className="mb-4 text-gray-600">
An unexpected error occurred. Please try again.
</p>
{process.env.NODE_ENV === 'development' && (
<details className="mb-4">
<summary className="cursor-pointer text-sm text-gray-500">
Error details
</summary>
<pre className="mt-2 overflow-auto rounded bg-gray-100 p-2 text-xs">
{error.message}
</pre>
{error.digest && (
<p className="mt-2 text-xs text-gray-500">
Error ID: {error.digest}
</p>
)}
</details>
)}
<div className="flex gap-2">
<button
onClick={reset}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Try again
</button>
<a
href="/"
className="rounded border border-gray-300 px-4 py-2 hover:bg-gray-50"
>
Go home
</a>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,41 @@
'use client';
import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<html>
<body>
<div className="flex min-h-screen items-center justify-center p-4">
<div className="max-w-md rounded-lg border p-6">
<h2 className="mb-2 text-2xl font-semibold">Application Error</h2>
<p className="mb-4 text-gray-600">
A critical error occurred. Please refresh the page.
</p>
<div className="flex gap-2">
<button
onClick={reset}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Reload page
</button>
</div>
</div>
</div>
</body>
</html>
);
}

View File

@@ -0,0 +1,9 @@
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config');
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config');
}
}

View File

@@ -0,0 +1,79 @@
import * as Sentry from '@sentry/nextjs';
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogContext {
[key: string]: unknown;
}
class Logger {
private log(level: LogLevel, message: string, context?: LogContext) {
// Add context as Sentry breadcrumb
if (context) {
Sentry.addBreadcrumb({
category: 'log',
message,
level,
data: context,
});
}
// Also log to console in development
if (process.env.NODE_ENV === 'development') {
const logFn = console[level] || console.log;
logFn(`[${level.toUpperCase()}] ${message}`, context || '');
}
}
debug(message: string, context?: LogContext) {
this.log('debug', message, context);
}
info(message: string, context?: LogContext) {
this.log('info', message, context);
}
warn(message: string, context?: LogContext) {
this.log('warn', message, context);
// Set context for next error
if (context) {
Sentry.setContext('warning', context);
}
}
error(message: string, context?: LogContext & { error?: Error }) {
this.log('error', message, context);
// Capture as Sentry error with context
const error = context?.error || new Error(message);
Sentry.captureException(error, {
level: 'error',
contexts: {
custom: context,
},
});
}
// Set user context for all subsequent logs/errors
setUser(user: { id: string; email?: string; username?: string }) {
Sentry.setUser(user);
}
// Clear user context (e.g., on logout)
clearUser() {
Sentry.setUser(null);
}
// Add custom tags for filtering in Sentry
setTag(key: string, value: string) {
Sentry.setTag(key, value);
}
// Add context that persists across logs
setContext(key: string, context: LogContext) {
Sentry.setContext(key, context);
}
}
export const logger = new Logger();

View File

@@ -0,0 +1,60 @@
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Set environment
environment: process.env.NODE_ENV,
// Adjust sample rate for production
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
// Capture 100% of errors
sampleRate: 1.0,
// Disable in development
enabled: process.env.NODE_ENV !== 'development',
// Session Replay - captures user interactions for debugging
replaysSessionSampleRate: 0.1, // 10% of sessions
replaysOnErrorSampleRate: 1.0, // 100% of sessions with errors
integrations: [
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],
// Filter out noise
beforeSend(event, hint) {
// Don't send in development
if (process.env.NODE_ENV === 'development') {
return null;
}
// Filter out specific client errors
const error = hint.originalException;
if (error && typeof error === 'object' && 'message' in error) {
const message = String(error.message);
// Common browser errors to ignore
if (
message.includes('ResizeObserver') ||
message.includes('Non-Error promise rejection') ||
message.includes('ChunkLoadError')
) {
return null;
}
}
return event;
},
// Add custom tags
initialScope: {
tags: {
runtime: 'client',
},
},
});

View File

@@ -0,0 +1,56 @@
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
// Set environment
environment: process.env.NODE_ENV,
// Adjust sample rate for production in production
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
// Capture 100% of errors
sampleRate: 1.0,
// Disable in development to reduce noise
enabled: process.env.NODE_ENV !== 'development',
// Capture Server Actions and API routes
integrations: [
Sentry.rewriteFramesIntegration({
root: process.cwd(),
}),
],
// Filter out noise
beforeSend(event, hint) {
// Don't send errors from development
if (process.env.NODE_ENV === 'development') {
return null;
}
// Filter out specific errors
const error = hint.originalException;
if (error && typeof error === 'object' && 'message' in error) {
const message = String(error.message);
// Ignore known non-critical errors
if (
message.includes('ResizeObserver') ||
message.includes('NotFoundError') ||
message.includes('AbortError')
) {
return null;
}
}
return event;
},
// Add custom tags
initialScope: {
tags: {
runtime: 'server',
},
},
});

View File

@@ -0,0 +1,358 @@
# OpenTelemetry Integration
## What is OpenTelemetry?
OpenTelemetry (OTel) is an observability framework for:
- **Traces**: Track requests through distributed systems
- **Metrics**: Measure system performance
- **Logs**: Structured event logging
Sentry acts as an OTel backend, receiving and visualizing this data.
## How It Works with Sentry
1. **Instrumentation hook** (`instrumentation.ts`) initializes OTel
2. **Automatic instrumentation** tracks HTTP, database, and framework operations
3. **Custom instrumentation** adds application-specific tracing
4. **Sentry integration** sends traces to Sentry for visualization
## Automatic Instrumentation
### What Gets Traced Automatically
With Sentry + Next.js:
**Server-side:**
- HTTP requests (incoming/outgoing)
- Database queries (Prisma, PostgreSQL)
- File system operations
- Next.js Server Components
- API routes
**Client-side:**
- Navigation
- User interactions
- Fetch requests
- Component rendering (partial)
### Viewing Traces
In Sentry dashboard:
1. Go to Performance
2. Click on a transaction
3. View waterfall of spans (operations)
4. Identify bottlenecks
## Custom Instrumentation
### 1. Creating Spans
Track specific operations:
```typescript
import * as Sentry from '@sentry/nextjs';
export async function processWorldData(worldId: string) {
return await Sentry.startSpan(
{
name: 'process-world-data',
op: 'task',
attributes: {
worldId,
},
},
async () => {
// Your logic here
const data = await fetchWorldData(worldId);
const processed = await processData(data);
return processed;
}
);
}
```
### 2. Nested Spans
Track sub-operations:
```typescript
export async function generateWorldContent(worldId: string) {
return await Sentry.startSpan(
{ name: 'generate-world-content', op: 'task' },
async (span) => {
// Child span 1
const entities = await Sentry.startSpan(
{ name: 'fetch-entities', op: 'db.query' },
async () => {
return await prisma.entity.findMany({
where: { worldId },
});
}
);
// Child span 2
const content = await Sentry.startSpan(
{ name: 'generate-content', op: 'ai' },
async () => {
return await generateContent(entities);
}
);
span.setStatus('ok');
return content;
}
);
}
```
### 3. Adding Attributes
Enrich spans with metadata:
```typescript
Sentry.startSpan(
{
name: 'export-world',
op: 'export',
attributes: {
worldId: 'world-123',
format: 'json',
includeAssets: true,
},
},
async (span) => {
const result = await exportWorld();
// Add result metadata
span.setAttributes({
'export.size': result.size,
'export.entityCount': result.entityCount,
});
return result;
}
);
```
## Distributed Tracing
### Propagating Context
Trace requests across services:
```typescript
// Service A - initiates request
export async function callServiceB() {
return await Sentry.startSpan(
{ name: 'call-service-b', op: 'http.client' },
async () => {
// Context automatically propagated via headers
const response = await fetch('https://service-b.com/api', {
headers: {
// Sentry automatically adds tracing headers
},
});
return response.json();
}
);
}
// Service B - receives request
// Continues the same trace automatically if instrumented
```
### Trace Visualization
In Sentry:
- See complete request flow across services
- Identify slow external calls
- Understand service dependencies
## Common Span Operations
Use semantic operation names:
```typescript
// Database operations
{ op: 'db.query' }
{ op: 'db.insert' }
{ op: 'db.update' }
// HTTP operations
{ op: 'http.client' }
{ op: 'http.server' }
// Business logic
{ op: 'task' }
{ op: 'function' }
// External services
{ op: 'ai' }
{ op: 'cache' }
{ op: 'storage' }
```
## Performance Patterns
### 1. Identify N+1 Queries
Traces reveal repeated database queries:
```typescript
// Bad - N+1 problem shows in trace
for (const world of worlds) {
const entities = await prisma.entity.findMany({
where: { worldId: world.id },
});
}
// Trace shows: db.query x N times
// Good - Single query
const entities = await prisma.entity.findMany({
where: {
worldId: { in: worlds.map(w => w.id) },
},
});
// Trace shows: db.query x 1 time
```
### 2. Parallel Operations
Visualize parallel vs sequential:
```typescript
// Sequential - slow
const terrain = await generateTerrain();
const biomes = await generateBiomes();
const climate = await generateClimate();
// Trace: 3 sequential spans
// Parallel - faster
const [terrain, biomes, climate] = await Promise.all([
generateTerrain(),
generateBiomes(),
generateClimate(),
]);
// Trace: 3 parallel spans
```
### 3. Caching Effectiveness
Track cache hits/misses:
```typescript
export async function getCachedData(key: string) {
return await Sentry.startSpan(
{ name: 'get-cached-data', op: 'cache' },
async (span) => {
const cached = await redis.get(key);
if (cached) {
span.setAttributes({ 'cache.hit': true });
return cached;
}
span.setAttributes({ 'cache.hit': false });
const data = await fetchFreshData();
await redis.set(key, data);
return data;
}
);
}
```
## Sampling Strategies
### 1. Transaction-Based Sampling
Sample different operations differently:
```typescript
Sentry.init({
tracesSampler: (samplingContext) => {
// High-value operations - trace all
if (samplingContext.name?.includes('checkout')) {
return 1.0;
}
// Background tasks - sample less
if (samplingContext.name?.includes('background')) {
return 0.01;
}
// Default
return 0.1;
},
});
```
### 2. Head-Based Sampling
Decision made at trace start (Sentry default).
**Pros**: Simple, consistent per trace
**Cons**: Can't sample based on outcome
### 3. Performance Impact
- Minimal overhead (<5% typically)
- Sampling reduces overhead
- Most cost is in data transmission
## Troubleshooting
### Traces Not Appearing
1. Check `tracesSampleRate` > 0
2. Verify instrumentation.ts exports `register()`
3. Ensure Next.js experimental.instrumentationHook enabled
4. Check Sentry DSN is correct
### Missing Spans
1. Verify automatic instrumentation is enabled
2. Check custom spans use correct Sentry API
3. Ensure spans finish (call `.finish()` or use callback form)
### Incomplete Traces
1. Check trace context is propagated (especially cross-service)
2. Verify all services use compatible Sentry SDK versions
3. Review sampling - some spans may be dropped
## Best Practices
1. **Use semantic operation names** - Makes filtering/grouping easier
2. **Add relevant attributes** - Enrich spans with context
3. **Don't over-instrument** - Focus on meaningful operations
4. **Keep spans short** - Track discrete operations, not long processes
5. **Set span status** - Mark success/failure explicitly
6. **Sample appropriately** - Balance visibility with cost
7. **Review regularly** - Check for performance regressions
## Advanced: Custom Metrics
Track custom measurements:
```typescript
import * as Sentry from '@sentry/nextjs';
// Record custom metric
Sentry.metrics.distribution('world.entity.count', entityCount, {
tags: {
worldType: 'fantasy',
},
});
// Track over time in Sentry dashboard
```
## Integration with Other Tools
Sentry can forward traces to:
- **DataDog** - Full observability platform
- **Jaeger** - Distributed tracing
- **Prometheus** - Metrics collection
Configure via Sentry integrations or OpenTelemetry Collector.

View File

@@ -0,0 +1,439 @@
# Sentry Best Practices
## Error Handling Patterns
### 1. Structured Error Handling
Use consistent error handling across the application:
```typescript
import { logger } from '@/lib/logger';
import * as Sentry from '@sentry/nextjs';
export async function performOperation() {
try {
// Operation logic
const result = await someOperation();
logger.info('Operation succeeded', { result });
return result;
} catch (error) {
// Log with context
logger.error('Operation failed', {
error: error as Error,
operation: 'performOperation',
timestamp: new Date().toISOString(),
});
// Re-throw for caller to handle
throw error;
}
}
```
### 2. User-Facing vs Internal Errors
Distinguish between errors shown to users and internal errors:
```typescript
class UserFacingError extends Error {
constructor(
message: string,
public userMessage: string,
public statusCode: number = 400
) {
super(message);
this.name = 'UserFacingError';
}
}
// Usage
try {
await createEntity(data);
} catch (error) {
if (error instanceof UserFacingError) {
// Show to user
return { error: error.userMessage };
}
// Internal error - log to Sentry, show generic message
logger.error('Entity creation failed', { error });
return { error: 'An unexpected error occurred' };
}
```
### 3. Add Context to Errors
Enrich errors with relevant context:
```typescript
export async function updateEntity(entityId: string, data: unknown) {
// Set context for all errors in this scope
Sentry.setContext('entity', {
id: entityId,
type: 'character',
data,
});
try {
const result = await prisma.entity.update({
where: { id: entityId },
data,
});
return result;
} catch (error) {
// Error automatically includes entity context
logger.error('Entity update failed', { error, entityId });
throw error;
}
}
```
## Performance Monitoring
### 1. Custom Transactions
Track important operations:
```typescript
import * as Sentry from '@sentry/nextjs';
export async function generateWorld(params: WorldParams) {
const transaction = Sentry.startTransaction({
name: 'generate-world',
op: 'task',
tags: {
worldType: params.type,
complexity: params.complexity,
},
});
try {
// Track each step
const terrainSpan = transaction.startChild({
op: 'generate-terrain',
description: 'Generate terrain data',
});
const terrain = await generateTerrain(params);
terrainSpan.finish();
const biomesSpan = transaction.startChild({
op: 'generate-biomes',
description: 'Generate biome data',
});
const biomes = await generateBiomes(terrain);
biomesSpan.finish();
transaction.setStatus('ok');
return { terrain, biomes };
} catch (error) {
transaction.setStatus('internal_error');
throw error;
} finally {
transaction.finish();
}
}
```
### 2. Database Query Monitoring
Track slow queries:
```typescript
import { prisma } from '@/lib/prisma';
// Prisma automatically creates spans for queries when OTel is configured
const users = await prisma.user.findMany({
where: { active: true },
include: { profile: true },
});
// Shows up in Sentry as:
// - db.query
// - duration
// - query details
```
### 3. API Call Monitoring
Track external API calls:
```typescript
export async function fetchExternalData(url: string) {
return await Sentry.startSpan(
{
name: 'external-api-call',
op: 'http.client',
attributes: {
'http.url': url,
},
},
async () => {
const response = await fetch(url);
// Add response details to span
Sentry.getCurrentScope().setContext('http', {
status: response.status,
statusText: response.statusText,
});
return response.json();
}
);
}
```
## Quota Management
### 1. Sample Rates
Adjust sampling to control quota usage:
```typescript
Sentry.init({
// Errors
sampleRate: 1.0, // 100% of errors
// Performance monitoring
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
// Session replay
replaysSessionSampleRate: 0.05, // 5% of normal sessions
replaysOnErrorSampleRate: 1.0, // 100% of error sessions
});
```
### 2. Filter Out Noise
Prevent known non-issues from consuming quota:
```typescript
Sentry.init({
beforeSend(event, hint) {
const error = hint.originalException;
// Filter by error message
if (error && typeof error === 'object' && 'message' in error) {
const message = String(error.message);
const ignoredPatterns = [
'ResizeObserver loop',
'Non-Error promise rejection',
'Loading chunk',
'Script error.',
];
if (ignoredPatterns.some((pattern) => message.includes(pattern))) {
return null;
}
}
// Filter by URL
if (event.request?.url?.includes('localhost')) {
return null;
}
return event;
},
});
```
### 3. Inbound Filters
Configure in Sentry dashboard:
- Settings > Inbound Filters
- Filter by:
- Browser version
- Error message
- Release version
- IP address
## User Context
### 1. Set User on Authentication
```typescript
// In middleware or auth utility
import * as Sentry from '@sentry/nextjs';
import { getCurrentUser } from '@/lib/auth/utils';
export async function setUserContext() {
const user = await getCurrentUser();
if (user) {
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
});
}
}
```
### 2. Clear User on Logout
```typescript
export async function logout() {
// Clear Sentry context
Sentry.setUser(null);
logger.clearUser();
// Logout logic
await supabase.auth.signOut();
}
```
## Breadcrumbs
Track user journey:
```typescript
// Navigation
Sentry.addBreadcrumb({
category: 'navigation',
message: 'User navigated to world editor',
level: 'info',
data: { worldId: 'world-123' },
});
// User actions
Sentry.addBreadcrumb({
category: 'user.action',
message: 'Created new character',
level: 'info',
data: {
characterName: 'Hero',
worldId: 'world-123',
},
});
// Data changes
Sentry.addBreadcrumb({
category: 'data',
message: 'Updated world settings',
level: 'info',
data: {
worldId: 'world-123',
settings: { theme: 'dark' },
},
});
```
## Source Maps
### 1. Verify Upload
Check source maps are uploaded:
```bash
# Build with source maps
npm run build
# Verify in Sentry dashboard
# Settings > Source Maps > [Release]
```
### 2. Configure Properly
```typescript
// next.config.js
const { withSentryConfig } = require('@sentry/nextjs');
module.exports = withSentryConfig(
nextConfig,
{
// Sentry webpack plugin options
silent: true,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
},
{
// Sentry SDK options
hideSourceMaps: true, // Don't expose source maps to public
widenClientFileUpload: true, // Upload more files for better stack traces
disableLogger: true, // Reduce noise in build logs
}
);
```
### 3. Troubleshooting
**Source maps not working:**
- Verify `SENTRY_AUTH_TOKEN` is set
- Check build logs for upload errors
- Ensure release version matches between app and Sentry
## Alerting
### 1. Configure Alert Rules
In Sentry dashboard:
- Alerts > Create Alert Rule
- Set conditions (frequency, affected users)
- Choose notification channels (email, Slack, PagerDuty)
**Recommended alerts:**
- High error rate (>10 errors/minute)
- New error type
- Regression (error in new release)
- Performance degradation
### 2. Alert Fatigue
Avoid alert fatigue:
- Use appropriate thresholds
- Filter out noisy errors
- Set up different alerts for different severity
- Use digest emails instead of immediate notifications
## Release Tracking
Associate errors with releases:
```typescript
Sentry.init({
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || 'dev',
environment: process.env.NODE_ENV,
});
```
**Benefits:**
- Track which release introduced errors
- Compare error rates between releases
- Verify if deploy fixed issues
## Testing
### 1. Test Error Capture
```typescript
// Test error handling
export async function testSentry() {
try {
throw new Error('Test error from Sentry setup');
} catch (error) {
logger.error('Testing Sentry integration', { error });
}
}
```
### 2. Verify in Dashboard
After testing:
1. Go to Sentry dashboard
2. Check Issues for test error
3. Verify context, breadcrumbs, and user info
4. Check source maps resolve correctly
## Common Pitfalls
1. **Not filtering development errors** - Always disable Sentry in development or filter out
2. **Missing source maps** - Stack traces are unreadable without them
3. **Not setting user context** - Makes debugging user-specific issues hard
4. **Over-sampling in production** - Wastes quota and money
5. **Ignoring performance monitoring** - Only tracking errors misses slow operations
6. **Not reviewing regularly** - Set aside time weekly to review Sentry issues