All files / src/lib/monitoring withMonitoring.ts

0% Statements 0/97
100% Branches 0/0
0% Functions 0/1
0% Lines 0/97

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98                                                                                                                                                                                                   
export const dynamic = "force-dynamic";

import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { logger, type LogCategory } from '@/lib/logging';
import crypto from 'crypto';

type RouteHandler = (
  request: NextRequest,
  context?: { params: Promise<Record<string, string>> }
) => Promise<NextResponse>;

interface MonitoringOptions {
  name: string;
  category?: LogCategory;
  slowThreshold?: number; // ms, default 1000
  logSuccessful?: boolean; // default true
}

export function withMonitoring(
  handler: RouteHandler,
  options: MonitoringOptions
): RouteHandler {
  const {
    name,
    category = 'API',
    slowThreshold = 1000,
    logSuccessful = true} = options;

  return async (request, context) => {
    const startTime = performance.now();
    const requestId = request.headers.get('x-request-id') || crypto.randomUUID();

    // Extract user info if available (from session/auth)
    let userId: number | undefined;

    try {
      const response = await handler(request, context);
      const duration = performance.now() - startTime;

      // Log request
      if (logSuccessful) {
        logger.info(`${request.method} ${name}`, {
          category,
          requestId,
          duration: Number(duration.toFixed(2)),
          statusCode: response.status,
          method: request.method,
          url: request.url});
      }

      // Store performance metric
      if (process.env.NODE_ENV === 'production') {
        prisma.performanceMetric.create({
          data: {
            type: 'api',
            name,
            duration,
            method: request.method,
            statusCode: response.status,
            userId,
            requestId}}).catch(() => {}); // Non-blocking
      }

      // Log slow requests
      if (duration > slowThreshold) {
        logger.warn(`Slow API: ${name}`, {
          category,
          requestId,
          duration: Number(duration.toFixed(2)),
          method: request.method,
          url: request.url});
      }

      // Add request ID to response headers
      const headers = new Headers(response.headers);
      headers.set('x-request-id', requestId);

      return new NextResponse(response.body, {
        status: response.status,
        statusText: response.statusText,
        headers});
    } catch (error) {
      const duration = performance.now() - startTime;

      logger.error(`${request.method} ${name} failed`, error instanceof Error ? error : new Error(String(error)), {
        category,
        requestId,
        duration: Number(duration.toFixed(2)),
        method: request.method,
        url: request.url,
        statusCode: 500});

      throw error;
    }
  };
}