Error Handling and Logging
Relevant source files
The following files were used as context for generating this wiki page:
The Vinesight application employs a multi-layered error handling and logging strategy to ensure robustness, provide a good user experience, and facilitate debugging. The system combines centralized error management services, React-specific error boundaries for the UI, and dedicated error pages for specific failure scenarios. This approach allows for graceful failure recovery, user-friendly error messages, and detailed logging for developers.
The core of the system is a centralized ErrorHandler class, which standardizes error objects and categorizes them based on their source (e.g., API, Database). This is complemented by a React ErrorBoundary component that catches rendering errors, and a useErrorHandler hook to streamline error management in asynchronous operations within functional components.
Centralized Error Handling
The primary mechanism for handling logic, API, and database errors is the ErrorHandler singleton class. This class ensures a consistent approach to creating, categorizing, and logging errors throughout the application.
Sources: src/lib/error-handler.ts
The AppError Interface
All errors processed by the ErrorHandler are standardized into the AppError object format. This provides a consistent structure for error information.
| Field | Type | Description |
|---|---|---|
code | string | A unique code identifying the error type (e.g., DATABASE_ERROR, UNAUTHORIZED). |
message | string | A descriptive message about the error. |
details | any | The original error object or any other relevant details for debugging. |
timestamp | number | The Unix timestamp (milliseconds) when the error was created. |
userFriendly | boolean | A flag indicating if the message is safe and suitable for display to the end-user. |
Sources: src/lib/error-handler.ts:2-8
ErrorHandler Singleton
The ErrorHandler is implemented as a singleton to provide a single, global instance for error management. This prevents multiple error histories and ensures consistent state.
The diagram below shows the class structure for the ErrorHandler and the related RetryHandler.
Sources: src/lib/error-handler.ts:10-141
Error Categorization
The ErrorHandler class contains specific methods to handle errors from different sources, translating raw errors into standardized AppError objects.
Database Errors
The handleDatabaseError method maps specific PostgreSQL error codes to user-friendly AppError objects.
| Code | AppError.code | User-Friendly Message |
|---|---|---|
PGRST116 | NOT_FOUND | The requested data was not found |
23505 | DUPLICATE_ENTRY | This entry already exists |
23503 | FOREIGN_KEY_VIOLATION | Cannot complete this operation due to related data |
| Other | DATABASE_ERROR | A database error occurred. Please try again. |
Sources: src/lib/error-handler.ts:51-74
API Errors
The handleApiError method categorizes errors based on HTTP status codes or error names.
| Status/Name | AppError.code | User-Friendly Message |
|---|---|---|
ValidationError | VALIDATION_ERROR | The provided data is invalid |
401 | UNAUTHORIZED | You need to sign in to perform this action |
403 | FORBIDDEN | You do not have permission to perform this action |
429 | RATE_LIMITED | Too many requests. Please wait a moment and try again. |
>= 500 | SERVER_ERROR | Server error. Please try again later. |
| Other | UNKNOWN_ERROR | An unexpected error occurred. Please try again. |
Sources: src/lib/error-handler.ts:77-111
Error History
The ErrorHandler maintains an in-memory history of the last 100 errors. This is intended for debugging purposes and can be accessed via the getErrorHistory() method.
Sources: src/lib/error-handler.ts:12, 38-41, 133-135
Asynchronous Operations
To simplify error handling for asynchronous actions like API calls, the system provides a custom React hook and a retry utility.
Sources: src/lib/error-handler.ts
useErrorHandler Hook
This hook is the primary tool for managing async operations within React components. It encapsulates loading state, error state, and the execution logic.
The hook returns an object with the following properties:
| Property | Type | Description |
|---|---|---|
error | AppError | null | The current error state. null if no error. |
isLoading | boolean | true while the async operation is in progress. |
handleAsync | function | A wrapper function to execute an async operation with centralized error handling. |
clearError | function | A function to reset the error state to null. |
getUserMessage | function | A utility to get a user-friendly message from an AppError object. |
Sources: src/lib/error-handler.ts:146-190
The handleAsync function is the core of the hook. The sequence diagram below illustrates its execution flow.
Sources: src/lib/error-handler.ts:151-178
RetryHandler Utility
The RetryHandler class provides a static withRetry method to automatically retry a failed async operation with exponential backoff.
// src/lib/error-handler.ts:144-145
export class RetryHandler {
static async withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000,
): Promise<T> {
// ... implementation
}
}
This utility can be composed with the useErrorHandler hook to build resilient data-fetching logic.
Sources: src/lib/error-handler.ts:144-167
React Component-Level Error Handling
The application uses React's built-in error boundary mechanism to catch JavaScript errors in the component tree, log them, and display a fallback UI.
Sources: src/components/ErrorBoundary.tsx, src/app/layout.tsx
ErrorBoundary Component
This is a class-based React component that implements getDerivedStateFromError and componentDidCatch. It acts as a catch-all for rendering errors in any of its child components.
The following diagram shows the lifecycle of the ErrorBoundary when an error occurs.
When an error is caught, it renders a fallback UI that includes:
- An error message.
- Action buttons: "Try Again", "Reload Page", "Go Home".
- In development mode, detailed error information and a component stack trace.
Sources: src/components/ErrorBoundary.tsx:64-122
Root Layout Integration
The entire application is wrapped in an AsyncErrorBoundary within the root layout. This ensures that any unhandled rendering error or unhandled promise rejection anywhere in the app will be caught.
// src/app/layout.tsx:112-124
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
{/* ... */}
<body /* ... */>
<AsyncErrorBoundary>
<Suspense fallback={/* ... */}>
<GlobalAuthErrorHandler />
<I18nProvider>
<LayoutContent>{children}</LayoutContent>
</I18nProvider>
</Suspense>
</AsyncErrorBoundary>
{/* ... */}
</body>
</html>
)
}
The AsyncErrorBoundary component also sets up a global event listener for unhandledrejection events, ensuring that promise rejections that are not caught locally are still logged.
Sources: src/app/layout.tsx:118-124, src/components/ErrorBoundary.tsx:130-144
Application-Specific Error Display
In addition to global error boundaries, the application displays errors inline for better user context, particularly in forms and authentication flows.
Inline Form Errors
The login and signup forms use the useSupabaseAuth hook, which exposes an error state. This state is used to conditionally render an <Alert> component directly within the form, providing immediate feedback without a full page reload.
// src/app/login/page.tsx:50-54
{showError && error && (
<Alert className="mb-4 border-red-200 bg-red-50">
<AlertDescription className="text-red-800">{error}</AlertDescription>
</Alert>
)}
This pattern is used in both the login and signup forms to show authentication-related errors, such as "Invalid login credentials" or password mismatches.
Sources: src/app/login/page.tsx:50-54, src/components/auth/SignupForm.tsx:88-92
Dedicated Error Pages
For critical failures where inline display is not possible, the application uses dedicated error pages. An example is the authentication code exchange failure page.
The AuthCodeErrorContent component reads the error query parameter from the URL and displays a human-readable message corresponding to the error code.
// src/app/auth/auth-code-error/page.tsx:17-30
const getErrorMessage = (errorCode: string | null) => {
switch (errorCode) {
case 'exchange_failed':
return 'Failed to exchange authorization code. This might be a temporary issue.'
case 'no_code':
return 'No authorization code received from the authentication provider.'
case 'access_denied':
return 'You denied access to the application. Please try again if you want to sign in.'
// ...
default:
return 'There was an error during the authentication process. This is often resolved by trying again.'
}
}
This provides a clear explanation and recovery options for users encountering problems during OAuth flows.
Sources: src/app/auth/auth-code-error/page.tsx:17-30
Summary
Vinesight's error handling system is a comprehensive, layered architecture. It combines a centralized ErrorHandler for standardizing backend and logic errors, a useErrorHandler hook for managing async operations in the UI, a global ErrorBoundary to prevent application crashes from rendering errors, and context-specific UI to provide clear user feedback. This design ensures that errors are handled gracefully, users are informed, and developers have the necessary information to diagnose and fix issues.