Monitoring

Guide for implementing monitoring in ShipKit applications, including error tracking, performance monitoring, logging, and analytics

Monitoring Guide

This guide covers monitoring strategies and best practices for ShipKit applications.

Error Tracking

Sentry Integration

// src/lib/monitoring/sentry.ts
import * as Sentry from '@sentry/nextjs'
import { env } from '@/env.mjs'

Sentry.init({
  dsn: env.NEXT_PUBLIC_SENTRY_DSN,
  environment: env.NODE_ENV,
  tracesSampleRate: 1.0,
  replaysOnErrorSampleRate: 1.0,
  replaysSessionSampleRate: 0.1,
  integrations: [
    new Sentry.Replay({
      maskAllText: true,
      blockAllMedia: true,
    }),
  ],
})

export function captureError(
  error: Error,
  context?: Record<string, any>
) {
  if (error instanceof Error) {
    Sentry.captureException(error, {
      extra: context,
    })
  } else {
    Sentry.captureMessage(String(error), {
      extra: context,
    })
  }
}

// Usage in error boundary
export function ErrorBoundary({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <Sentry.ErrorBoundary
      fallback={({ error }) => (
        <div className="error-container">
          <h2>Something went wrong</h2>
          <pre>{error.message}</pre>
        </div>
      )}
    >
      {children}
    </Sentry.ErrorBoundary>
  )
}

Error Handling

// src/lib/monitoring/errors.ts
import { captureError } from './sentry'

export class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number = 500,
    public context?: Record<string, any>
  ) {
    super(message)
    this.name = 'AppError'
  }
}

export function handleError(error: unknown) {
  if (error instanceof AppError) {
    captureError(error, error.context)
    return {
      code: error.code,
      message: error.message,
      statusCode: error.statusCode,
    }
  }

  if (error instanceof Error) {
    captureError(error)
    return {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
      statusCode: 500,
    }
  }

  captureError(new Error('Unknown error'))
  return {
    code: 'UNKNOWN_ERROR',
    message: 'An unknown error occurred',
    statusCode: 500,
  }
}

// Usage in API routes
export async function GET(req: Request) {
  try {
    // API logic
  } catch (error) {
    const { code, message, statusCode } = handleError(error)
    return new Response(
      JSON.stringify({ error: { code, message } }),
      { status: statusCode }
    )
  }
}

Performance Monitoring

Web Vitals

// src/lib/monitoring/web-vitals.ts
import { onCLS, onFID, onLCP, onTTFB } from 'web-vitals'
import { captureError } from './sentry'

export function reportWebVitals() {
  try {
    onCLS((metric) => sendToAnalytics('CLS', metric))
    onFID((metric) => sendToAnalytics('FID', metric))
    onLCP((metric) => sendToAnalytics('LCP', metric))
    onTTFB((metric) => sendToAnalytics('TTFB', metric))
  } catch (error) {
    captureError(error)
  }
}

function sendToAnalytics(
  name: string,
  metric: { value: number; id: string }
) {
  const body = {
    name,
    value: metric.value,
    id: metric.id,
    page: window.location.pathname,
  }

  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/analytics/vitals', JSON.stringify(body))
  } else {
    fetch('/api/analytics/vitals', {
      method: 'POST',
      body: JSON.stringify(body),
      keepalive: true,
    })
  }
}

// Usage in app
export function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  useEffect(() => {
    reportWebVitals()
  }, [])

  return <>{children}</>
}

Performance Metrics

// src/lib/monitoring/metrics.ts
import { Gauge, Counter, register } from 'prom-client'
import { env } from '@/env.mjs'

// Initialize metrics
const httpRequestDuration = new Gauge({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
})

const httpRequestTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'route', 'status_code'],
})

// Middleware for tracking metrics
export async function metricsMiddleware(
  req: NextApiRequest,
  res: NextApiResponse,
  next: () => Promise<void>
) {
  const start = Date.now()

  try {
    await next()
  } finally {
    const duration = (Date.now() - start) / 1000

    httpRequestDuration
      .labels(req.method!, req.url!, res.statusCode.toString())
      .set(duration)

    httpRequestTotal
      .labels(req.method!, req.url!, res.statusCode.toString())
      .inc()
  }
}

// Metrics endpoint
export async function GET(req: Request) {
  try {
    const metrics = await register.metrics()
    return new Response(metrics, {
      headers: {
        'Content-Type': register.contentType,
      },
    })
  } catch (error) {
    captureError(error)
    return new Response('Error collecting metrics', {
      status: 500,
    })
  }
}

Analytics

Event Tracking

// src/lib/monitoring/analytics.ts
import { Analytics } from '@vercel/analytics/react'
import { env } from '@/env.mjs'

export function trackEvent(
  name: string,
  properties?: Record<string, any>
) {
  if (typeof window === 'undefined') return

  try {
    // Track with Vercel Analytics
    window.va?.track(name, properties)

    // Track with custom analytics
    fetch('/api/analytics/events', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        event: name,
        properties,
        timestamp: new Date().toISOString(),
        page: window.location.pathname,
      }),
    })
  } catch (error) {
    captureError(error)
  }
}

// Usage in components
export function AnalyticsProvider({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <Analytics />
      {children}
    </>
  )
}

// Track page views
export function usePageView() {
  const pathname = usePathname()

  useEffect(() => {
    trackEvent('page_view', { path: pathname })
  }, [pathname])
}

User Analytics

// src/lib/monitoring/user-analytics.ts
import { redis } from '@/lib/redis'
import { logger } from './logger'

interface UserSession {
  userId: string
  sessionId: string
  startTime: number
  lastActive: number
  pages: string[]
}

export async function trackUserSession(
  userId: string,
  sessionId: string,
  page: string
) {
  const key = `session:${sessionId}`

  try {
    const session = await redis.get<UserSession>(key)

    if (session) {
      session.lastActive = Date.now()
      session.pages.push(page)
      await redis.set(key, session, { ex: 1800 }) // 30 minutes
    } else {
      const newSession: UserSession = {
        userId,
        sessionId,
        startTime: Date.now(),
        lastActive: Date.now(),
        pages: [page],
      }
      await redis.set(key, newSession, { ex: 1800 })
    }
  } catch (error) {
    logger.error({ error }, 'Failed to track user session')
  }
}

// Usage in middleware
export async function middleware(req: NextRequest) {
  const userId = req.headers.get('x-user-id')
  const sessionId = req.headers.get('x-session-id')

  if (userId && sessionId) {
    await trackUserSession(
      userId,
      sessionId,
      req.nextUrl.pathname
    )
  }

  return NextResponse.next()
}

Monitoring Best Practices

  1. Error Tracking

    • Track all errors
    • Add context
    • Set up alerts
    • Monitor trends
  2. Performance

    • Track core vitals
    • Monitor resources
    • Set baselines
    • Alert on degradation
  3. Logging

    • Use structured logs
    • Include context
    • Rotate logs
    • Monitor storage
  4. Analytics

    • Track key metrics
    • Monitor trends
    • Set up dashboards
    • Review regularly

Monitoring Checklist

  1. Setup

    • [ ] Error tracking
    • [ ] Performance monitoring
    • [ ] Logging system
    • [ ] Analytics tracking
  2. Configuration

    • [ ] Alert thresholds
    • [ ] Log levels
    • [ ] Sampling rates
    • [ ] Retention policies
  3. Integration

    • [ ] Error boundaries
    • [ ] Middleware
    • [ ] API routes
    • [ ] Client tracking
  4. Maintenance

    • [ ] Review alerts
    • [ ] Clean up logs
    • [ ] Update dashboards
    • [ ] Optimize tracking