mirror of
https://github.com/sickn33/antigravity-awesome-skills.git
synced 2026-04-25 17:25:12 +02:00
337 lines
7.6 KiB
Markdown
337 lines
7.6 KiB
Markdown
# Sentry Integration and Monitoring
|
|
|
|
Complete guide to error tracking and performance monitoring with Sentry v8.
|
|
|
|
## Table of Contents
|
|
|
|
- [Core Principles](#core-principles)
|
|
- [Sentry Initialization](#sentry-initialization)
|
|
- [Error Capture Patterns](#error-capture-patterns)
|
|
- [Performance Monitoring](#performance-monitoring)
|
|
- [Cron Job Monitoring](#cron-job-monitoring)
|
|
- [Error Context Best Practices](#error-context-best-practices)
|
|
- [Common Mistakes](#common-mistakes)
|
|
|
|
---
|
|
|
|
## Core Principles
|
|
|
|
**MANDATORY**: All errors MUST be captured to Sentry. No exceptions.
|
|
|
|
**ALL ERRORS MUST BE CAPTURED** - Use Sentry v8 with comprehensive error tracking across all services.
|
|
|
|
---
|
|
|
|
## Sentry Initialization
|
|
|
|
### instrument.ts Pattern
|
|
|
|
**Location:** `src/instrument.ts` (MUST be first import in server.ts and all cron jobs)
|
|
|
|
**Template for Microservices:**
|
|
|
|
```typescript
|
|
import * as Sentry from '@sentry/node';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as ini from 'ini';
|
|
|
|
const sentryConfigPath = path.join(__dirname, '../sentry.ini');
|
|
const sentryConfig = ini.parse(fs.readFileSync(sentryConfigPath, 'utf-8'));
|
|
|
|
Sentry.init({
|
|
dsn: sentryConfig.sentry?.dsn,
|
|
environment: process.env.NODE_ENV || 'development',
|
|
tracesSampleRate: parseFloat(sentryConfig.sentry?.tracesSampleRate || '0.1'),
|
|
profilesSampleRate: parseFloat(sentryConfig.sentry?.profilesSampleRate || '0.1'),
|
|
|
|
integrations: [
|
|
...Sentry.getDefaultIntegrations({}),
|
|
Sentry.extraErrorDataIntegration({ depth: 5 }),
|
|
Sentry.localVariablesIntegration(),
|
|
Sentry.requestDataIntegration({
|
|
include: {
|
|
cookies: false,
|
|
data: true,
|
|
headers: true,
|
|
ip: true,
|
|
query_string: true,
|
|
url: true,
|
|
user: { id: true, email: true, username: true },
|
|
},
|
|
}),
|
|
Sentry.consoleIntegration(),
|
|
Sentry.contextLinesIntegration(),
|
|
Sentry.prismaIntegration(),
|
|
],
|
|
|
|
beforeSend(event, hint) {
|
|
// Filter health checks
|
|
if (event.request?.url?.includes('/healthcheck')) {
|
|
return null;
|
|
}
|
|
|
|
// Scrub sensitive headers
|
|
if (event.request?.headers) {
|
|
delete event.request.headers['authorization'];
|
|
delete event.request.headers['cookie'];
|
|
}
|
|
|
|
// Mask emails for PII
|
|
if (event.user?.email) {
|
|
event.user.email = event.user.email.replace(/^(.{2}).*(@.*)$/, '$1***$2');
|
|
}
|
|
|
|
return event;
|
|
},
|
|
|
|
ignoreErrors: [
|
|
/^Invalid JWT/,
|
|
/^JWT expired/,
|
|
'NetworkError',
|
|
],
|
|
});
|
|
|
|
// Set service context
|
|
Sentry.setTags({
|
|
service: 'form',
|
|
version: '1.0.1',
|
|
});
|
|
|
|
Sentry.setContext('runtime', {
|
|
node_version: process.version,
|
|
platform: process.platform,
|
|
});
|
|
```
|
|
|
|
**Critical Points:**
|
|
- PII protection built-in (beforeSend)
|
|
- Filter non-critical errors
|
|
- Comprehensive integrations
|
|
- Prisma instrumentation
|
|
- Service-specific tagging
|
|
|
|
---
|
|
|
|
## Error Capture Patterns
|
|
|
|
### 1. BaseController Pattern
|
|
|
|
```typescript
|
|
// Use BaseController.handleError
|
|
protected handleError(error: unknown, res: Response, context: string, statusCode = 500): void {
|
|
Sentry.withScope((scope) => {
|
|
scope.setTag('controller', this.constructor.name);
|
|
scope.setTag('operation', context);
|
|
scope.setUser({ id: res.locals?.claims?.userId });
|
|
Sentry.captureException(error);
|
|
});
|
|
|
|
res.status(statusCode).json({
|
|
success: false,
|
|
error: { message: error instanceof Error ? error.message : 'Error occurred' }
|
|
});
|
|
}
|
|
```
|
|
|
|
### 2. Workflow Error Handling
|
|
|
|
```typescript
|
|
import { SentryHelper } from '../utils/sentryHelper';
|
|
|
|
try {
|
|
await businessOperation();
|
|
} catch (error) {
|
|
SentryHelper.captureOperationError(error, {
|
|
operationType: 'POST_CREATION',
|
|
entityId: 123,
|
|
userId: 'user-123',
|
|
operation: 'createPost',
|
|
});
|
|
throw error;
|
|
}
|
|
```
|
|
|
|
### 3. Service Layer Error Handling
|
|
|
|
```typescript
|
|
try {
|
|
await someOperation();
|
|
} catch (error) {
|
|
Sentry.captureException(error, {
|
|
tags: {
|
|
service: 'form',
|
|
operation: 'someOperation'
|
|
},
|
|
extra: {
|
|
userId: currentUser.id,
|
|
entityId: 123
|
|
}
|
|
});
|
|
throw error;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Monitoring
|
|
|
|
### Database Performance Tracking
|
|
|
|
```typescript
|
|
import { DatabasePerformanceMonitor } from '../utils/databasePerformance';
|
|
|
|
const result = await DatabasePerformanceMonitor.withPerformanceTracking(
|
|
'findMany',
|
|
'UserProfile',
|
|
async () => {
|
|
return await PrismaService.main.userProfile.findMany({ take: 5 });
|
|
}
|
|
);
|
|
```
|
|
|
|
### API Endpoint Spans
|
|
|
|
```typescript
|
|
router.post('/operation', async (req, res) => {
|
|
return await Sentry.startSpan({
|
|
name: 'operation.execute',
|
|
op: 'http.server',
|
|
attributes: {
|
|
'http.method': 'POST',
|
|
'http.route': '/operation'
|
|
}
|
|
}, async () => {
|
|
const result = await performOperation();
|
|
res.json(result);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Cron Job Monitoring
|
|
|
|
### Mandatory Pattern
|
|
|
|
```typescript
|
|
#!/usr/bin/env node
|
|
import '../instrument'; // FIRST LINE after shebang
|
|
import * as Sentry from '@sentry/node';
|
|
|
|
async function main() {
|
|
return await Sentry.startSpan({
|
|
name: 'cron.job-name',
|
|
op: 'cron',
|
|
attributes: {
|
|
'cron.job': 'job-name',
|
|
'cron.startTime': new Date().toISOString(),
|
|
}
|
|
}, async () => {
|
|
try {
|
|
// Cron job logic here
|
|
} catch (error) {
|
|
Sentry.captureException(error, {
|
|
tags: {
|
|
'cron.job': 'job-name',
|
|
'error.type': 'execution_error'
|
|
}
|
|
});
|
|
console.error('[Cron] Error:', error);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
}
|
|
|
|
main().then(() => {
|
|
console.log('[Cron] Completed successfully');
|
|
process.exit(0);
|
|
}).catch((error) => {
|
|
console.error('[Cron] Fatal error:', error);
|
|
process.exit(1);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Error Context Best Practices
|
|
|
|
### Rich Context Example
|
|
|
|
```typescript
|
|
Sentry.withScope((scope) => {
|
|
// User context
|
|
scope.setUser({
|
|
id: user.id,
|
|
email: user.email,
|
|
username: user.username
|
|
});
|
|
|
|
// Tags for filtering
|
|
scope.setTag('service', 'form');
|
|
scope.setTag('endpoint', req.path);
|
|
scope.setTag('method', req.method);
|
|
|
|
// Structured context
|
|
scope.setContext('operation', {
|
|
type: 'workflow.complete',
|
|
workflowId: 123,
|
|
stepId: 456
|
|
});
|
|
|
|
// Breadcrumbs for timeline
|
|
scope.addBreadcrumb({
|
|
category: 'workflow',
|
|
message: 'Starting step completion',
|
|
level: 'info',
|
|
data: { stepId: 456 }
|
|
});
|
|
|
|
Sentry.captureException(error);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Common Mistakes
|
|
|
|
```typescript
|
|
// ❌ Swallowing errors
|
|
try {
|
|
await riskyOperation();
|
|
} catch (error) {
|
|
// Silent failure
|
|
}
|
|
|
|
// ❌ Generic error messages
|
|
throw new Error('Error occurred');
|
|
|
|
// ❌ Exposing sensitive data
|
|
Sentry.captureException(error, {
|
|
extra: { password: user.password } // NEVER
|
|
});
|
|
|
|
// ❌ Missing async error handling
|
|
async function bad() {
|
|
fetchData().then(data => processResult(data)); // Unhandled
|
|
}
|
|
|
|
// ✅ Proper async handling
|
|
async function good() {
|
|
try {
|
|
const data = await fetchData();
|
|
processResult(data);
|
|
} catch (error) {
|
|
Sentry.captureException(error);
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
**Related Files:**
|
|
- [SKILL.md](SKILL.md)
|
|
- [routing-and-controllers.md](routing-and-controllers.md)
|
|
- [async-and-errors.md](async-and-errors.md)
|