CentercodePorygon
DittoPorygonAPI

Monitoring

Observability, health checks, and alerting

SafeReliable

Overview

Monitor application health with health checks, track errors with structured logging, and use Vercel Analytics for performance insights.

What it is

Health check endpoints, Vercel Analytics integration, error tracking via logging, and alerting patterns.

Why we use it

Detect issues before users report them, understand performance bottlenecks, and respond quickly to incidents.

When to use

Always. Every production application needs monitoring. Set up health checks and alerting from day one.

Key Features

  • Health check endpoints for uptime monitoring
  • Vercel Analytics and Speed Insights
  • Error tracking via structured logging
  • Alerting patterns for critical issues

Quick Start

Health Check Endpoint

Create an endpoint that verifies all dependencies are healthy.

// Health check endpoint
// src/app/api/health/route.ts

import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';
import { logger } from '@/lib/logger';

export async function GET() {
  try {
    // Check database connection
    await prisma.$queryRaw`SELECT 1`;

    return NextResponse.json({
      ok: true,
      status: 'healthy',
      timestamp: new Date().toISOString(),
    });
  } catch (error) {
    logger.error('Health check failed', { error });
    return NextResponse.json(
      { ok: false, status: 'unhealthy' },
      { status: 503 }
    );
  }
}

Patterns

Vercel Analytics

Add Analytics and Speed Insights to your app.

// Vercel Analytics setup
// src/app/layout.tsx

import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  );
}

Error Tracking

Capture and log errors with context.

// Error tracking with logging
// src/lib/errors/handle-error.ts

import { logger } from '@/lib/logger';
import { captureException } from '@/lib/monitoring';

export function handleError(error: Error) {
  // Log locally
  logger.error('API error', {
    message: error.message,
    stack: error.stack,
    name: error.name,
  });

  // Send to external service (if configured)
  captureException(error);

  return NextResponse.json(
    { ok: false, error: formatError(error) },
    { status: getStatusCode(error) }
  );
}

Performance Monitoring

Track operation duration and alert on slowness.

// Performance tracking
import { logger } from '@/lib/logger';

export async function GET(request: Request) {
  const start = performance.now();

  try {
    const result = await expensiveOperation();

    const duration = performance.now() - start;
    logger.info('Operation completed', {
      duration,
      operation: 'expensiveOperation',
    });

    // Alert if slow
    if (duration > 5000) {
      logger.warn('Slow operation detected', { duration });
    }

    return NextResponse.json({ ok: true, data: result });
  } catch (error) {
    const duration = performance.now() - start;
    logger.error('Operation failed', { duration, error });
    throw error;
  }
}

Alerting Patterns

Set severity levels for automated alerting.

// Alerting patterns
// Use structured logging for alerting rules

// Critical: Immediate attention needed
logger.error('Database connection failed', {
  severity: 'critical',
  alert: true,
});

// Warning: Investigate soon
logger.warn('Rate limit threshold approaching', {
  current: 85,
  threshold: 100,
  alert: true,
});

// Info: For dashboards
logger.info('Daily report generated', {
  recordCount: 1500,
  duration: 3200,
});

Watch Out

Swallowing errors without logging

Don't

// Silent failure
try {
  await riskyOperation();
} catch (error) {
  // Error silently swallowed!
  return { data: null };
}

Do

// Logged failure with context
try {
  await riskyOperation();
} catch (error) {
  logger.error('Risky operation failed', {
    error,
    context: { userId, action },
  });
  throw new InternalError('Operation failed');
}

No health check endpoint for uptime monitoring

Don't

// No health check
// Vercel can't tell if your app is healthy
// Unhealthy pods stay in rotation

Do

// Comprehensive health check
export async function GET() {
  const checks = {
    database: await checkDatabase(),
    redis: await checkRedis(),
    external: await checkExternalAPI(),
  };

  const healthy = Object.values(checks).every(c => c.ok);

  return NextResponse.json(
    { ok: healthy, checks },
    { status: healthy ? 200 : 503 }
  );
}
  • Too many alerts causing important ones to be ignored
  • No visibility into system health

Related

Logging

Structured logging

Analytics

Product analytics

Deployment

Deploy workflow