From 2099329e2e1ef812509a8129feee962324fbacc9 Mon Sep 17 00:00:00 2001 From: oluwatobiloba olusanya Date: Fri, 29 May 2026 15:08:02 +0100 Subject: [PATCH] fix: wire error logging into registration system - Replace bare console.error in signup route with structured edgeLog calls at each branch (validation, conflict, success, unhandled error) - Add missing POST /api/errors/report route that errorReportingService already calls in production but had no handler - Wire errorReportingService into global-error.tsx so the app-level error boundary reports errors the same way error.tsx already does Closes #556 --- src/app/api/auth/signup/route.ts | 25 ++++++++++++++-------- src/app/api/errors/report/route.ts | 33 ++++++++++++++++++++++++++++++ src/app/global-error.tsx | 11 +++++++++- 3 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 src/app/api/errors/report/route.ts diff --git a/src/app/api/auth/signup/route.ts b/src/app/api/auth/signup/route.ts index f4209e4a..9fa203f3 100644 --- a/src/app/api/auth/signup/route.ts +++ b/src/app/api/auth/signup/route.ts @@ -14,27 +14,34 @@ export const runtime = 'edge'; export async function POST( request: NextRequest, ): Promise> { - edgeLog('info', '/api/auth/signup', 'POST request received'); + const route = '/api/auth/signup'; + edgeLog('info', route, 'POST request received'); const { addHeaders, rateLimitResponse } = withRateLimit(request, 'AUTH'); if (rateLimitResponse) return rateLimitResponse as NextResponse; try { const result = validateBody(SignupRequestSchema, await request.json()); - if (!result.ok) return addHeaders(result.error) as NextResponse; + if (!result.ok) { + edgeLog('warn', route, 'Validation failed', { reason: 'schema' }); + return addHeaders(result.error) as NextResponse; + } const { name, email, password, confirmPassword } = result.data; // Basic validation if (!name || !email || !password || !confirmPassword) { + edgeLog('warn', route, 'Validation failed', { reason: 'missing_fields' }); return addHeaders(NextResponse.json({ message: 'All fields are required' }, { status: 400 })); } if (password !== confirmPassword) { + edgeLog('warn', route, 'Validation failed', { reason: 'password_mismatch' }); return addHeaders(NextResponse.json({ message: "Passwords don't match" }, { status: 400 })); } if (password.length < 6) { + edgeLog('warn', route, 'Validation failed', { reason: 'password_too_short' }); return addHeaders( NextResponse.json({ message: 'Password must be at least 6 characters' }, { status: 400 }), ); @@ -42,27 +49,29 @@ export async function POST( // Mock: block already-registered email if (email === 'existing@teachlink.com') { + edgeLog('warn', route, 'Registration conflict', { reason: 'email_exists' }); return addHeaders( NextResponse.json({ message: 'Email already registered' }, { status: 409 }), ); } + const userId = Math.random().toString(36).substring(2, 9); + edgeLog('info', route, 'Account created', { userId }); + return addHeaders( NextResponse.json( { message: 'Account created successfully', - user: { - id: Math.random().toString(36).substring(2, 9), - name, - email, - }, + user: { id: userId, name, email }, token: `mock-jwt-token-${Date.now()}`, }, { status: 201 }, ), ); } catch (error) { - console.error('Signup error:', error); + edgeLog('error', route, 'Unhandled signup error', { + error: error instanceof Error ? error.message : String(error), + }); return addHeaders(NextResponse.json({ message: 'Internal server error' }, { status: 500 })); } diff --git a/src/app/api/errors/report/route.ts b/src/app/api/errors/report/route.ts new file mode 100644 index 00000000..0cdc1467 --- /dev/null +++ b/src/app/api/errors/report/route.ts @@ -0,0 +1,33 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { createLogger } from '@/lib/logging'; + +const logger = createLogger('errors.report'); + +export async function POST(request: NextRequest): Promise { + try { + const report = await request.json(); + + // Build a real Error so normalizeError captures name + message + stack properly + const clientError = report.errorData?.message + ? Object.assign(new Error(report.errorData.message), { + name: report.errorData.type ?? 'ClientError', + }) + : undefined; + + logger.error('Client error report', { + context: { + reportId: report.id, + sessionId: report.sessionId, + userId: report.userId, + url: report.url, + environment: report.environment, + }, + error: clientError, + }); + + return NextResponse.json({ ok: true }, { status: 200 }); + } catch (err) { + logger.warn('Failed to process error report', { error: err }); + return NextResponse.json({ ok: false }, { status: 400 }); + } +} diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index f0b4cfcb..df08212b 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -1,7 +1,8 @@ 'use client'; -import React from 'react'; +import React, { useEffect } from 'react'; import { UserFriendlyErrorDisplay } from '@/components/errors/UserFriendlyErrorDisplay'; +import { errorReportingService } from '@/services/errorReporting'; export default function GlobalError({ error, @@ -10,6 +11,14 @@ export default function GlobalError({ error: Error & { digest?: string }; reset: () => void; }) { + useEffect(() => { + errorReportingService.addBreadcrumb('global-error', { + errorMessage: error.message, + digest: error.digest, + }); + errorReportingService.reportError(error); + }, [error]); + return (