Observability: Add Sentry error tracking & performance monitoring #332

Closed
opened 2026-03-02 12:33:44 +00:00 by jack · 0 comments
Owner

Problem

Keine Error-Tracking → Production-Errors bleiben unsichtbar bis User melden.

Fehlende Insights:

  • Welche Errors treten häufig auf?
  • Wo crasht die App?
  • Performance-Bottlenecks?
  • User-Impact von Bugs?

Gewünschte Lösung: Sentry

Sentry bietet:

  • Error-Tracking (Stack-Traces, Breadcrumbs)
  • Performance-Monitoring (Transaction-Tracing)
  • Release-Tracking (welche Version hat Bugs?)
  • User-Feedback (optional)

Setup

pnpm add @sentry/node @sentry/profiling-node --filter @claude-mem/backend
pnpm add @sentry/react --filter @claude-mem/ui

Backend Integration

// packages/backend/src/index.ts
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV || 'development',
  release: process.env.npm_package_version,
  tracesSampleRate: 0.1, // 10% Performance-Sampling
  profilesSampleRate: 0.1,
  integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
    new Sentry.Integrations.Express({ app }),
    new Sentry.ProfilingIntegration()
  ],
  beforeSend(event, hint) {
    // Filter sensitive data
    if (event.request) {
      delete event.request.cookies;
      delete event.request.headers?.['x-api-key'];
    }
    return event;
  }
});

// Sentry Request-Handler (vor Routes)
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

// Routes...

// Sentry Error-Handler (nach Routes)
app.use(Sentry.Handlers.errorHandler());

UI Integration

// packages/ui/src/main.tsx
import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: import.meta.env.VITE_SENTRY_DSN,
  environment: import.meta.env.MODE,
  release: import.meta.env.VITE_APP_VERSION,
  integrations: [
    new Sentry.BrowserTracing(),
    new Sentry.Replay()
  ],
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0
});

// Error-Boundary
const App = Sentry.withProfiler(() => {
  // ... existing App
});

Worker Integration

// packages/worker/src/index.ts
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  beforeSend(event) {
    // Filter AI-API-Keys
    if (event.extra?.apiKey) {
      event.extra.apiKey = '***REDACTED***';
    }
    return event;
  }
});

// Wrap Task-Processing
async function processTask(task: Task) {
  return Sentry.startSpan(
    { name: `process:${task.type}`, op: 'task' },
    async () => {
      try {
        await executeTask(task);
      } catch (e) {
        Sentry.captureException(e, {
          tags: { taskType: task.type },
          extra: { taskId: task.id }
        });
        throw e;
      }
    }
  );
}

Environment-Konfiguration

# .env.example
SENTRY_DSN=https://xxx@sentry.io/yyy
SENTRY_ENVIRONMENT=production
SENTRY_TRACES_SAMPLE_RATE=0.1

Custom Instrumentation

Context/Tags

Sentry.setUser({ id: session.userId });
Sentry.setTag('project', session.projectPath);
Sentry.setContext('session', {
  sessionId: session.sessionId,
  observations: session.observationCount
});

Breadcrumbs

Sentry.addBreadcrumb({
  category: 'task',
  message: `Processing ${task.type}`,
  level: 'info',
  data: { taskId: task.id }
});

Performance-Spans

const transaction = Sentry.startTransaction({
  name: 'extract-observation',
  op: 'ai.task'
});

const span = transaction.startChild({ op: 'ai.api', description: 'Call Anthropic' });
await callAnthropicAPI();
span.finish();

transaction.finish();

Dashboard-Widgets

Wichtige Metriken:

  • Error-Rate (Errors/h)
  • Crash-Free-Rate (%)
  • Top-Errors (häufigste Exceptions)
  • Slow-Transactions (>1s)
  • Affected-Users

Alerts:

  • Error-Rate >10/min → Slack-Notification
  • Crash-Free <99% → Email-Alert
  • New Error-Type → Notify Developer

Privacy

Sensitive-Data-Scrubbing:

beforeSend(event) {
  // Entferne API-Keys, Tokens
  const sensitivePatterns = [/sk-ant-[a-z0-9]+/gi, /Bearer [a-zA-Z0-9]+/gi];
  
  if (event.message) {
    sensitivePatterns.forEach(pattern => {
      event.message = event.message!.replace(pattern, '***REDACTED***');
    });
  }
  
  return event;
}

Cost-Estimation

Sentry Pricing (Free Tier):

  • 5.000 Errors/Monat: Free
  • 10.000 Performance-Events/Monat: Free

Darüber:

  • $26/Monat (Team Plan)

Alternativen (Self-Hosted):

  • Sentry Self-Hosted (Docker Compose, komplex)
  • GlitchTip (Sentry-kompatibel, einfacher)

Acceptance Criteria

  • Sentry für Backend, Worker, UI
  • Sensitive-Data-Scrubbing
  • Release-Tracking (release: version)
  • Performance-Monitoring (10% Sampling)
  • Error-Alerts konfiguriert
  • Dashboard mit Widgets
  • Dokumentation

Priority

Medium - Wichtig für Production, aber nicht MVP-Blocker.

Alternative: Custom Logging

Falls Sentry zu teuer/komplex:

// Einfaches Error-Logging
logger.error('Task failed', { error, taskId, sessionId });
// → Logs zu File + externe Aggregation (Loki, etc.)
## Problem **Keine Error-Tracking** → Production-Errors bleiben unsichtbar bis User melden. **Fehlende Insights:** - Welche Errors treten häufig auf? - Wo crasht die App? - Performance-Bottlenecks? - User-Impact von Bugs? ## Gewünschte Lösung: Sentry **Sentry** bietet: - Error-Tracking (Stack-Traces, Breadcrumbs) - Performance-Monitoring (Transaction-Tracing) - Release-Tracking (welche Version hat Bugs?) - User-Feedback (optional) ### Setup ```bash pnpm add @sentry/node @sentry/profiling-node --filter @claude-mem/backend pnpm add @sentry/react --filter @claude-mem/ui ``` ### Backend Integration ```typescript // packages/backend/src/index.ts import * as Sentry from '@sentry/node'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV || 'development', release: process.env.npm_package_version, tracesSampleRate: 0.1, // 10% Performance-Sampling profilesSampleRate: 0.1, integrations: [ new Sentry.Integrations.Http({ tracing: true }), new Sentry.Integrations.Express({ app }), new Sentry.ProfilingIntegration() ], beforeSend(event, hint) { // Filter sensitive data if (event.request) { delete event.request.cookies; delete event.request.headers?.['x-api-key']; } return event; } }); // Sentry Request-Handler (vor Routes) app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.tracingHandler()); // Routes... // Sentry Error-Handler (nach Routes) app.use(Sentry.Handlers.errorHandler()); ``` ### UI Integration ```typescript // packages/ui/src/main.tsx import * as Sentry from '@sentry/react'; Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN, environment: import.meta.env.MODE, release: import.meta.env.VITE_APP_VERSION, integrations: [ new Sentry.BrowserTracing(), new Sentry.Replay() ], tracesSampleRate: 0.1, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0 }); // Error-Boundary const App = Sentry.withProfiler(() => { // ... existing App }); ``` ### Worker Integration ```typescript // packages/worker/src/index.ts import * as Sentry from '@sentry/node'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, beforeSend(event) { // Filter AI-API-Keys if (event.extra?.apiKey) { event.extra.apiKey = '***REDACTED***'; } return event; } }); // Wrap Task-Processing async function processTask(task: Task) { return Sentry.startSpan( { name: `process:${task.type}`, op: 'task' }, async () => { try { await executeTask(task); } catch (e) { Sentry.captureException(e, { tags: { taskType: task.type }, extra: { taskId: task.id } }); throw e; } } ); } ``` ## Environment-Konfiguration ```bash # .env.example SENTRY_DSN=https://xxx@sentry.io/yyy SENTRY_ENVIRONMENT=production SENTRY_TRACES_SAMPLE_RATE=0.1 ``` ## Custom Instrumentation ### Context/Tags ```typescript Sentry.setUser({ id: session.userId }); Sentry.setTag('project', session.projectPath); Sentry.setContext('session', { sessionId: session.sessionId, observations: session.observationCount }); ``` ### Breadcrumbs ```typescript Sentry.addBreadcrumb({ category: 'task', message: `Processing ${task.type}`, level: 'info', data: { taskId: task.id } }); ``` ### Performance-Spans ```typescript const transaction = Sentry.startTransaction({ name: 'extract-observation', op: 'ai.task' }); const span = transaction.startChild({ op: 'ai.api', description: 'Call Anthropic' }); await callAnthropicAPI(); span.finish(); transaction.finish(); ``` ## Dashboard-Widgets **Wichtige Metriken:** - Error-Rate (Errors/h) - Crash-Free-Rate (%) - Top-Errors (häufigste Exceptions) - Slow-Transactions (>1s) - Affected-Users **Alerts:** - Error-Rate >10/min → Slack-Notification - Crash-Free <99% → Email-Alert - New Error-Type → Notify Developer ## Privacy **Sensitive-Data-Scrubbing:** ```typescript beforeSend(event) { // Entferne API-Keys, Tokens const sensitivePatterns = [/sk-ant-[a-z0-9]+/gi, /Bearer [a-zA-Z0-9]+/gi]; if (event.message) { sensitivePatterns.forEach(pattern => { event.message = event.message!.replace(pattern, '***REDACTED***'); }); } return event; } ``` ## Cost-Estimation **Sentry Pricing (Free Tier):** - 5.000 Errors/Monat: Free - 10.000 Performance-Events/Monat: Free **Darüber:** - $26/Monat (Team Plan) **Alternativen (Self-Hosted):** - **Sentry Self-Hosted** (Docker Compose, komplex) - **GlitchTip** (Sentry-kompatibel, einfacher) ## Acceptance Criteria - [x] Sentry für Backend, Worker, UI - [x] Sensitive-Data-Scrubbing - [x] Release-Tracking (`release: version`) - [x] Performance-Monitoring (10% Sampling) - [x] Error-Alerts konfiguriert - [x] Dashboard mit Widgets - [x] Dokumentation ## Priority **Medium** - Wichtig für Production, aber nicht MVP-Blocker. ## Alternative: Custom Logging Falls Sentry zu teuer/komplex: ```typescript // Einfaches Error-Logging logger.error('Task failed', { error, taskId, sessionId }); // → Logs zu File + externe Aggregation (Loki, etc.) ```
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
customable/claude-mem#332
No description provided.