mirror of
https://github.com/ld3z/new-manga.git
synced 2026-11-09 20:53:45 -07:00
add more error logging for importing and vercel db improvements
This commit is contained in:
parent
f84046c66c
commit
80e7fe5c92
3 changed files with 126 additions and 7 deletions
|
|
@ -479,7 +479,23 @@ const languages = [
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
console.error(`Feed import failed with status ${response.status}: ${errorText}`);
|
console.error(`Feed import failed with status ${response.status}: ${errorText}`);
|
||||||
throw new Error(`Failed to fetch feed: ${response.status} ${response.statusText}`);
|
|
||||||
|
// Display a more user-friendly error based on the status code
|
||||||
|
let userFriendlyError = 'Failed to import feed.';
|
||||||
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
userFriendlyError = `Feed ID "${feedId}" not found. Please check the ID and try again.`;
|
||||||
|
} else if (response.status === 500) {
|
||||||
|
if (errorText.includes('ETIMEDOUT') || errorText.includes('timeout')) {
|
||||||
|
userFriendlyError = 'Connection to database timed out. This is common on the first request. Please try again in a few seconds.';
|
||||||
|
} else if (errorText.includes('ECONNREFUSED')) {
|
||||||
|
userFriendlyError = 'Database connection refused. Please try again later.';
|
||||||
|
} else {
|
||||||
|
userFriendlyError = 'Server error while importing feed. Please try again later.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(userFriendlyError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,11 @@ import Redis from 'ioredis';
|
||||||
import type { FeedMapping } from './types';
|
import type { FeedMapping } from './types';
|
||||||
import { getChaptersForSlugs } from './api';
|
import { getChaptersForSlugs } from './api';
|
||||||
|
|
||||||
|
// Extended Error interface for network errors
|
||||||
|
interface RedisNetworkError extends Error {
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Primary Redis configuration
|
// Primary Redis configuration
|
||||||
const PRIMARY_REDIS_URL = import.meta.env.PRIMARY_REDIS_URL || process.env.PRIMARY_REDIS_URL || process.env.REDIS_URL;
|
const PRIMARY_REDIS_URL = import.meta.env.PRIMARY_REDIS_URL || process.env.PRIMARY_REDIS_URL || process.env.REDIS_URL;
|
||||||
|
|
||||||
|
|
@ -39,6 +44,9 @@ let replicaClients: Map<string, Redis> = new Map();
|
||||||
const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 30; // 30 days
|
const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 30; // 30 days
|
||||||
const MAX_RETRIES = 5;
|
const MAX_RETRIES = 5;
|
||||||
const BASE_DELAY_MS = 100;
|
const BASE_DELAY_MS = 100;
|
||||||
|
const IS_VERCEL = process.env.VERCEL || false;
|
||||||
|
const VERCEL_CONNECTION_TIMEOUT = 15000; // 15 seconds for Vercel
|
||||||
|
const LOCAL_CONNECTION_TIMEOUT = 5000; // 5 seconds for local
|
||||||
|
|
||||||
// Improved connection handling with exponential backoff
|
// Improved connection handling with exponential backoff
|
||||||
async function createRedisClient(url: string): Promise<Redis> {
|
async function createRedisClient(url: string): Promise<Redis> {
|
||||||
|
|
@ -57,8 +65,14 @@ async function createRedisClient(url: string): Promise<Redis> {
|
||||||
maxRetriesPerRequest: null, // Allow retries for queued commands
|
maxRetriesPerRequest: null, // Allow retries for queued commands
|
||||||
enableOfflineQueue: true, // Enable command queuing
|
enableOfflineQueue: true, // Enable command queuing
|
||||||
enableReadyCheck: true,
|
enableReadyCheck: true,
|
||||||
|
connectTimeout: 15000, // Increase connection timeout to 15 seconds for Vercel's serverless environment
|
||||||
|
disconnectTimeout: 10000, // Increase disconnect timeout to 10 seconds
|
||||||
|
keepAlive: 10000, // Send keepalive every 10 seconds
|
||||||
|
commandTimeout: 10000, // Command timeout
|
||||||
|
retryMaxDelay: 5000, // Maximum delay between retries
|
||||||
reconnectOnError: (err) => {
|
reconnectOnError: (err) => {
|
||||||
return err.message.includes('READONLY');
|
const targetErrors = ['READONLY', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND'];
|
||||||
|
return targetErrors.some(e => err.message.includes(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -67,10 +81,21 @@ async function createRedisClient(url: string): Promise<Redis> {
|
||||||
|
|
||||||
// Get the best available Redis client
|
// Get the best available Redis client
|
||||||
export async function getRedisClient(): Promise<Redis> {
|
export async function getRedisClient(): Promise<Redis> {
|
||||||
|
// Special handling for Vercel environment
|
||||||
|
if (IS_VERCEL) {
|
||||||
|
console.log('Running in Vercel environment - using optimized Redis connection strategy');
|
||||||
|
}
|
||||||
|
|
||||||
// Try primary first
|
// Try primary first
|
||||||
if (primaryClient?.status === 'ready') {
|
if (primaryClient?.status === 'ready') {
|
||||||
try {
|
try {
|
||||||
await primaryClient.ping();
|
// Add timeout to ping
|
||||||
|
const pingPromise = primaryClient.ping();
|
||||||
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Ping timeout')), 3000)
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.race([pingPromise, timeoutPromise]);
|
||||||
return primaryClient;
|
return primaryClient;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Primary Redis connection failed, will try replicas:', error);
|
console.warn('Primary Redis connection failed, will try replicas:', error);
|
||||||
|
|
@ -82,7 +107,13 @@ export async function getRedisClient(): Promise<Redis> {
|
||||||
for (const [url, client] of replicaClients.entries()) {
|
for (const [url, client] of replicaClients.entries()) {
|
||||||
if (client.status === 'ready') {
|
if (client.status === 'ready') {
|
||||||
try {
|
try {
|
||||||
await client.ping();
|
// Add timeout to ping
|
||||||
|
const pingPromise = client.ping();
|
||||||
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Ping timeout')), 3000)
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.race([pingPromise, timeoutPromise]);
|
||||||
console.log(`Using ${getRedisName(url)}`);
|
console.log(`Using ${getRedisName(url)}`);
|
||||||
return client;
|
return client;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -99,7 +130,18 @@ export async function getRedisClient(): Promise<Redis> {
|
||||||
|
|
||||||
primaryClient
|
primaryClient
|
||||||
.on('ready', () => console.log('Primary Redis connection ready'))
|
.on('ready', () => console.log('Primary Redis connection ready'))
|
||||||
.on('error', (err) => console.error('Primary Redis error:', err))
|
.on('error', (err: RedisNetworkError) => {
|
||||||
|
// Log more details for connection errors
|
||||||
|
if (err.code === 'ETIMEDOUT') {
|
||||||
|
console.error('Primary Redis connection timeout. This is common in serverless environments like Vercel. Check your network connectivity and Redis server availability.');
|
||||||
|
} else if (err.code === 'ECONNREFUSED') {
|
||||||
|
console.error('Primary Redis connection refused. Ensure the Redis server is running and the URL is correct.');
|
||||||
|
} else if (err.code === 'ENOTFOUND') {
|
||||||
|
console.error('Primary Redis host not found. Check your PRIMARY_REDIS_URL environment variable.');
|
||||||
|
} else {
|
||||||
|
console.error('Primary Redis error:', err);
|
||||||
|
}
|
||||||
|
})
|
||||||
.on('reconnecting', () => console.log('Primary Redis reconnecting'))
|
.on('reconnecting', () => console.log('Primary Redis reconnecting'))
|
||||||
.on('end', () => console.log('Primary Redis connection closed'));
|
.on('end', () => console.log('Primary Redis connection closed'));
|
||||||
|
|
||||||
|
|
@ -107,6 +149,42 @@ export async function getRedisClient(): Promise<Redis> {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to connect to primary Redis:', error);
|
console.error('Failed to connect to primary Redis:', error);
|
||||||
|
|
||||||
|
// Special handling for Vercel ETIMEDOUT errors
|
||||||
|
if (IS_VERCEL && error instanceof Error && error.message.includes('ETIMEDOUT')) {
|
||||||
|
console.log('Encountered ETIMEDOUT in Vercel environment. Retrying with longer timeout...');
|
||||||
|
try {
|
||||||
|
// Try one more time with increased timeout
|
||||||
|
const tempClient = new Redis(PRIMARY_REDIS_URL!, {
|
||||||
|
connectTimeout: 20000, // Increased timeout for last-ditch effort
|
||||||
|
retryStrategy: () => null // No retries for this attempt
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for ready or error
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
tempClient.disconnect();
|
||||||
|
reject(new Error('Final connection attempt timed out'));
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
tempClient.once('ready', () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
tempClient.once('error', (err) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we get here, connection was successful
|
||||||
|
primaryClient = tempClient;
|
||||||
|
return tempClient;
|
||||||
|
} catch (finalError) {
|
||||||
|
console.error('Final Redis connection attempt failed:', finalError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try connecting to any replica that might be available
|
// Try connecting to any replica that might be available
|
||||||
for (const url of REPLICA_URLS) {
|
for (const url of REPLICA_URLS) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -116,7 +194,18 @@ export async function getRedisClient(): Promise<Redis> {
|
||||||
|
|
||||||
client
|
client
|
||||||
.on('ready', () => console.log(`${redisName} connection ready`))
|
.on('ready', () => console.log(`${redisName} connection ready`))
|
||||||
.on('error', (err) => console.error(`${redisName} error:`, err))
|
.on('error', (err: RedisNetworkError) => {
|
||||||
|
// Log more details for connection errors
|
||||||
|
if (err.code === 'ETIMEDOUT') {
|
||||||
|
console.error(`${redisName} connection timeout. This is common in serverless environments like Vercel. Check your network connectivity and Redis server availability.`);
|
||||||
|
} else if (err.code === 'ECONNREFUSED') {
|
||||||
|
console.error(`${redisName} connection refused. Ensure the Redis server is running and the URL is correct.`);
|
||||||
|
} else if (err.code === 'ENOTFOUND') {
|
||||||
|
console.error(`${redisName} host not found. Check your Redis URL environment variables.`);
|
||||||
|
} else {
|
||||||
|
console.error(`${redisName} error:`, err);
|
||||||
|
}
|
||||||
|
})
|
||||||
.on('reconnecting', () => console.log(`${redisName} reconnecting`))
|
.on('reconnecting', () => console.log(`${redisName} reconnecting`))
|
||||||
.on('end', () => console.log(`${redisName} connection closed`));
|
.on('end', () => console.log(`${redisName} connection closed`));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,20 @@ export const GET: APIRoute = async ({ params }) => {
|
||||||
? `Internal server error: ${error.message}`
|
? `Internal server error: ${error.message}`
|
||||||
: "Internal server error";
|
: "Internal server error";
|
||||||
|
|
||||||
return new Response(errorMessage, { status: 500 });
|
// Add specific error codes for better client-side handling
|
||||||
|
let statusCode = 500;
|
||||||
|
let friendlyMessage = errorMessage;
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.message.includes('ETIMEDOUT')) {
|
||||||
|
friendlyMessage = `Database connection timeout. This is often temporary, especially after server idle periods. Please try again in a few seconds.`;
|
||||||
|
} else if (error.message.includes('ECONNREFUSED')) {
|
||||||
|
friendlyMessage = `Database connection refused. The service may be temporarily unavailable.`;
|
||||||
|
} else if (error.message.includes('ENOTFOUND')) {
|
||||||
|
friendlyMessage = `Database host not found. There may be a configuration issue.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(friendlyMessage, { status: statusCode });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue