Skip to content

setup-monitoring

Access: /template setup-monitoring or /t setup-monitoring

Configures comprehensive monitoring, error tracking, and performance monitoring for your MakerKit application using industry-standard tools.

What It Does

The setup-monitoring command helps you:

  • Set up error tracking with Sentry
  • Configure performance monitoring
  • Implement custom metrics and logging
  • Add health check endpoints
  • Create monitoring dashboards
  • Set up alerting rules

Usage

bash
/template setup-monitoring "Description"
# or
/t setup-monitoring "Description"

When prompted, specify:

  • Monitoring services to configure (Sentry, LogRocket, etc.)
  • Performance monitoring requirements
  • Custom metrics to track
  • Alert thresholds

Prerequisites

  • A MakerKit project deployed or ready for deployment
  • Monitoring service accounts (Sentry, etc.)
  • Commercial MakerKit license from MakerKit

What Gets Created

1. Sentry Configuration

Error tracking setup:

typescript
// lib/monitoring/sentry.ts
import * as Sentry from '@sentry/nextjs';

export function initSentry() {
  Sentry.init({
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
    environment: process.env.NODE_ENV,
    
    // Performance Monitoring
    tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
    
    // Session Replay
    replaysSessionSampleRate: 0.1,
    replaysOnErrorSampleRate: 1.0,
    
    // Release tracking
    release: process.env.NEXT_PUBLIC_APP_VERSION,
    
    // Integrations
    integrations: [
      new Sentry.BrowserTracing({
        tracingOrigins: ['localhost', /^https:\/\/yourapp\.com/],
        routingInstrumentation: Sentry.nextRouterInstrumentation,
      }),
      new Sentry.Replay({
        maskAllText: false,
        blockAllMedia: false,
      }),
    ],
    
    // Filtering
    beforeSend(event, hint) {
      // Filter out non-critical errors
      if (event.exception) {
        const error = hint.originalException;
        
        // Ignore certain errors
        if (error?.message?.includes('ResizeObserver')) {
          return null;
        }
      }
      
      // Add user context
      if (event.user) {
        event.user = {
          ...event.user,
          ip_address: '{{auto}}',
        };
      }
      
      return event;
    },
  });
}

// Error boundary wrapper
export function withErrorBoundary<P extends object>(
  Component: React.ComponentType<P>,
  fallback?: React.ComponentType<any>
) {
  return Sentry.withErrorBoundary(Component, {
    fallback: fallback || ErrorFallback,
    showDialog: process.env.NODE_ENV === 'production',
  });
}

2. Performance Monitoring

Track key metrics:

typescript
// lib/monitoring/performance.ts
import { metrics } from '@opentelemetry/api-metrics';

const meter = metrics.getMeter('makerkit-app');

// Custom metrics
export const apiLatency = meter.createHistogram('api_latency', {
  description: 'API endpoint latency',
  unit: 'ms',
});

export const dbQueryDuration = meter.createHistogram('db_query_duration', {
  description: 'Database query duration',
  unit: 'ms',
});

export const activeUsers = meter.createUpDownCounter('active_users', {
  description: 'Currently active users',
});

// Measure API performance
export function measureApiPerformance(
  endpoint: string,
  method: string
) {
  const startTime = performance.now();
  
  return {
    end: (statusCode: number) => {
      const duration = performance.now() - startTime;
      
      apiLatency.record(duration, {
        endpoint,
        method,
        status_code: statusCode.toString(),
      });
      
      // Log slow requests
      if (duration > 1000) {
        console.warn(`Slow API request: ${method} ${endpoint} took ${duration}ms`);
      }
    },
  };
}

// Database query tracking
export function trackDatabaseQuery(
  query: string,
  duration: number
) {
  dbQueryDuration.record(duration, {
    query_type: getQueryType(query),
  });
}

3. Custom Logger

Structured logging:

typescript
// lib/monitoring/logger.ts
import pino from 'pino';
import { logflarePinoVercel } from 'pino-logflare';

// Create logger based on environment
const createLogger = () => {
  if (process.env.NODE_ENV === 'production') {
    const { stream, send } = logflarePinoVercel({
      apiKey: process.env.LOGFLARE_API_KEY!,
      sourceToken: process.env.LOGFLARE_SOURCE_TOKEN!,
    });
    
    return pino(
      {
        level: 'info',
        base: {
          env: process.env.NODE_ENV,
          revision: process.env.VERCEL_GITHUB_COMMIT_SHA,
        },
      },
      stream
    );
  }
  
  return pino({
    level: 'debug',
    transport: {
      target: 'pino-pretty',
      options: {
        colorize: true,
      },
    },
  });
};

export const logger = createLogger();

// Typed logger methods
export const log = {
  info: (msg: string, data?: any) => logger.info(data, msg),
  error: (msg: string, error?: any) => logger.error(error, msg),
  warn: (msg: string, data?: any) => logger.warn(data, msg),
  debug: (msg: string, data?: any) => logger.debug(data, msg),
};

// Request logging middleware
export function requestLogger(req: Request) {
  const url = new URL(req.url);
  
  log.info('Incoming request', {
    method: req.method,
    path: url.pathname,
    query: Object.fromEntries(url.searchParams),
    headers: Object.fromEntries(req.headers),
  });
}

4. Health Check Endpoints

Monitor service health:

typescript
// app/api/health/route.ts
import { NextResponse } from 'next/server';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { redis } from '@/lib/redis';

export async function GET() {
  const checks = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: process.env.NEXT_PUBLIC_APP_VERSION,
    checks: {} as Record<string, any>,
  };
  
  // Database check
  try {
    const client = getSupabaseServerClient();
    const { error } = await client.from('users').select('count').limit(1);
    
    checks.checks.database = {
      status: error ? 'unhealthy' : 'healthy',
      error: error?.message,
    };
  } catch (error) {
    checks.checks.database = {
      status: 'unhealthy',
      error: error.message,
    };
  }
  
  // Redis check
  try {
    await redis.ping();
    checks.checks.redis = { status: 'healthy' };
  } catch (error) {
    checks.checks.redis = {
      status: 'unhealthy',
      error: error.message,
    };
  }
  
  // Stripe check
  try {
    const stripe = getStripeInstance();
    await stripe.products.list({ limit: 1 });
    checks.checks.stripe = { status: 'healthy' };
  } catch (error) {
    checks.checks.stripe = {
      status: 'unhealthy',
      error: error.message,
    };
  }
  
  // Overall status
  const hasUnhealthy = Object.values(checks.checks).some(
    (check: any) => check.status === 'unhealthy'
  );
  
  if (hasUnhealthy) {
    checks.status = 'degraded';
  }
  
  return NextResponse.json(checks, {
    status: hasUnhealthy ? 503 : 200,
  });
}

5. Client-Side Monitoring

Track user interactions:

typescript
// lib/monitoring/client-monitoring.ts
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
import * as Sentry from '@sentry/nextjs';

// Page view tracking
export function usePageTracking() {
  const pathname = usePathname();
  
  useEffect(() => {
    // Track page view
    if (window.gtag) {
      window.gtag('config', process.env.NEXT_PUBLIC_GA_ID, {
        page_path: pathname,
      });
    }
    
    // Track in Sentry
    Sentry.addBreadcrumb({
      type: 'navigation',
      category: 'pageview',
      message: `Viewed ${pathname}`,
      level: 'info',
    });
  }, [pathname]);
}

// Performance observer
export function usePerformanceMonitoring() {
  useEffect(() => {
    // Core Web Vitals
    if ('web-vital' in window) {
      const { getCLS, getFID, getLCP, getFCP, getTTFB } = window['web-vital'];
      
      getCLS(sendToAnalytics);
      getFID(sendToAnalytics);
      getLCP(sendToAnalytics);
      getFCP(sendToAnalytics);
      getTTFB(sendToAnalytics);
    }
    
    // Long tasks
    if ('PerformanceObserver' in window) {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          // Log tasks longer than 50ms
          if (entry.duration > 50) {
            Sentry.addBreadcrumb({
              category: 'performance',
              message: `Long task detected: ${entry.duration}ms`,
              level: 'warning',
            });
          }
        }
      });
      
      observer.observe({ entryTypes: ['longtask'] });
      
      return () => observer.disconnect();
    }
  }, []);
}

6. Monitoring Dashboard

Admin monitoring view:

typescript
// app/admin/monitoring/page.tsx
export default function MonitoringDashboard() {
  const { data: metrics } = useSystemMetrics();
  const { data: errors } = useRecentErrors();
  
  return (
    <div className="space-y-6">
      <SystemHealthCard health={metrics?.health} />
      
      <div className="grid gap-6 md:grid-cols-2">
        <Card>
          <CardHeader>
            <CardTitle>Response Times</CardTitle>
          </CardHeader>
          <CardContent>
            <ResponseTimeChart data={metrics?.responseTime} />
          </CardContent>
        </Card>
        
        <Card>
          <CardHeader>
            <CardTitle>Error Rate</CardTitle>
          </CardHeader>
          <CardContent>
            <ErrorRateChart data={metrics?.errorRate} />
          </CardContent>
        </Card>
      </div>
      
      <Card>
        <CardHeader>
          <CardTitle>Recent Errors</CardTitle>
        </CardHeader>
        <CardContent>
          <ErrorsList errors={errors} />
        </CardContent>
      </Card>
    </div>
  );
}

7. Alert Configuration

Set up monitoring alerts:

typescript
// lib/monitoring/alerts.ts
interface AlertRule {
  name: string;
  condition: (metrics: SystemMetrics) => boolean;
  message: string;
  severity: 'info' | 'warning' | 'critical';
}

export const alertRules: AlertRule[] = [
  {
    name: 'high_error_rate',
    condition: (metrics) => metrics.errorRate > 0.05, // 5%
    message: 'Error rate exceeds 5%',
    severity: 'critical',
  },
  {
    name: 'slow_response_time',
    condition: (metrics) => metrics.p95ResponseTime > 2000, // 2s
    message: 'P95 response time exceeds 2 seconds',
    severity: 'warning',
  },
  {
    name: 'database_connection_issues',
    condition: (metrics) => metrics.dbErrors > 10,
    message: 'Multiple database connection errors',
    severity: 'critical',
  },
];

// Check alerts and notify
export async function checkAlerts() {
  const metrics = await getSystemMetrics();
  
  for (const rule of alertRules) {
    if (rule.condition(metrics)) {
      await sendAlert({
        rule: rule.name,
        message: rule.message,
        severity: rule.severity,
        metrics,
      });
    }
  }
}

8. Custom Metrics Dashboard

Business metrics tracking:

typescript
// components/monitoring/business-metrics.tsx
export function BusinessMetrics() {
  const metrics = useBusinessMetrics();
  
  return (
    <div className="grid gap-4 md:grid-cols-4">
      <MetricCard
        title="Daily Active Users"
        value={metrics.dau}
        change={metrics.dauChange}
        format="number"
      />
      <MetricCard
        title="Conversion Rate"
        value={metrics.conversionRate}
        change={metrics.conversionChange}
        format="percentage"
      />
      <MetricCard
        title="Average Session Duration"
        value={metrics.avgSessionDuration}
        change={metrics.sessionChange}
        format="duration"
      />
      <MetricCard
        title="Churn Rate"
        value={metrics.churnRate}
        change={metrics.churnChange}
        format="percentage"
        inverse // Lower is better
      />
    </div>
  );
}

Environment Variables

Required monitoring configuration:

bash
# Sentry
NEXT_PUBLIC_SENTRY_DSN=
SENTRY_ORG=
SENTRY_PROJECT=
SENTRY_AUTH_TOKEN=

# Logging
LOGFLARE_API_KEY=
LOGFLARE_SOURCE_TOKEN=

# Analytics
NEXT_PUBLIC_GA_ID=
NEXT_PUBLIC_MIXPANEL_TOKEN=

# Monitoring
DATADOG_API_KEY=
NEW_RELIC_LICENSE_KEY=

Testing Monitoring

typescript
describe('Monitoring', () => {
  it('captures errors correctly', async () => {
    const testError = new Error('Test error');
    Sentry.captureException(testError);
    
    // Verify error was sent
    expect(Sentry.getCurrentHub().getClient()).toBeDefined();
  });
  
  it('tracks performance metrics', async () => {
    const metric = measureApiPerformance('/api/test', 'GET');
    await delay(100);
    metric.end(200);
    
    // Verify metric was recorded
    expect(apiLatency.record).toHaveBeenCalled();
  });
});

Best Practices

  1. Sample appropriately: Don't track 100% in production
  2. Filter noise: Ignore non-actionable errors
  3. Set budgets: Define performance budgets
  4. Alert wisely: Avoid alert fatigue
  5. Document metrics: Explain what each metric means

License Requirement

Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.

Built with ❤️ for the AI Coding community, by Praney Behl