chore: add host info of relevant URL to all errors

This commit is contained in:
WebStreamr 2025-10-24 14:26:54 +00:00
parent 77164dfaa7
commit cc3d30be2f
No known key found for this signature in database
9 changed files with 73 additions and 41 deletions

View file

@ -1,11 +1,13 @@
export class HttpError extends Error {
public readonly url: URL;
public readonly status: number;
public readonly statusText: string;
public readonly headers: Record<string, string[] | string | undefined>;
public constructor(status: number, statusText: string, headers: Record<string, string[] | string | undefined>) {
public constructor(url: URL, status: number, statusText: string, headers: Record<string, string[] | string | undefined>) {
super();
this.url = url;
this.status = status;
this.statusText = statusText;
this.headers = headers;

View file

@ -1 +1,9 @@
export class QueueIsFullError extends Error {}
export class QueueIsFullError extends Error {
public readonly url: URL;
public constructor(url: URL) {
super();
this.url = url;
}
}

View file

@ -1 +1,9 @@
export class TimeoutError extends Error {}
export class TimeoutError extends Error {
public readonly url: URL;
public constructor(url: URL) {
super();
this.url = url;
}
}

View file

@ -1,9 +1,11 @@
export class TooManyRequestsError extends Error {
public readonly url: URL;
public readonly retryAfter: number;
public constructor(retryAfter: number) {
public constructor(url: URL, retryAfter: number) {
super();
this.url = url;
this.retryAfter = retryAfter;
}
}

View file

@ -1 +1,9 @@
export class TooManyTimeoutsError extends Error {}
export class TooManyTimeoutsError extends Error {
public readonly url: URL;
public constructor(url: URL) {
super();
this.url = url;
}
}

View file

@ -21,46 +21,50 @@ export const logErrorAndReturnNiceString = (ctx: Context, logger: winston.Logger
return '⚠️ MediaFlow Proxy authentication failed. Please set the correct password.';
}
logger.warn(`${source}: Request to ${error.url.href} was blocked, reason: ${error.reason}, headers: ${JSON.stringify(error.headers)}.`, ctx);
logger.warn(`${source}: Request to ${error.url} was blocked, reason: ${error.reason}, headers: ${JSON.stringify(error.headers)}.`, ctx);
return `⚠️ Request was blocked. Reason: ${error.reason}`;
return `⚠️ Request to ${error.url.host} was blocked. Reason: ${error.reason}`;
}
if (error instanceof TooManyRequestsError) {
logger.warn(`${source}: Rate limited for ${error.retryAfter} seconds.`, ctx);
logger.warn(`${source}: Request to ${error.url} was rate limited for ${error.retryAfter} seconds.`, ctx);
return '🚦 Request was rate-limited. Please try again later or consider self-hosting.';
return `🚦 Request to ${error.url.host} was rate-limited. Please try again later or consider self-hosting.`;
}
if (error instanceof TooManyTimeoutsError) {
logger.warn(`${source}: Too many timeouts.`, ctx);
logger.warn(`${source}: Too many timeouts when requesting ${error.url}.`, ctx);
return '🚦 Too many recent timeouts. Please try again later.';
return `🚦 Too many recent timeouts when requesting ${error.url.host}. Please try again later.`;
}
if (
error instanceof TimeoutError
|| (error instanceof DOMException && ['AbortError', 'TimeoutError'].includes(error.name)) // sometimes this gets through, no idea why..
) {
if (error instanceof TimeoutError) {
logger.warn(`${source}: Request to ${error.url} timed out.`, ctx);
return `🐢 Request to ${error.url.host} timed out.`;
}
if (error instanceof DOMException && ['AbortError', 'TimeoutError'].includes(error.name)) {
// sometimes this gets through, no idea why..
logger.warn(`${source}: Request timed out.`, ctx);
return '🐢 Request timed out.';
}
if (error instanceof QueueIsFullError) {
logger.warn(`${source}: Request queue is full.`, ctx);
logger.warn(`${source}: Request queue for ${error.url.host} is full.`, ctx);
return '⏳ Request queue is full. Please try again later or consider self-hosting.';
return `⏳ Request queue for ${error.url.host} is full. Please try again later or consider self-hosting.`;
}
if (error instanceof HttpError) {
logger.error(`${source}: HTTP status ${error.status} (${error.statusText}), headers: ${JSON.stringify(error.headers)}, stack: ${error.stack}.`, ctx);
logger.error(`${source}: Error when requesting url ${error.url}, HTTP status ${error.status} (${error.statusText}), headers: ${JSON.stringify(error.headers)}, stack: ${error.stack}.`, ctx);
if (error.status >= 500) {
return `❌ Remote server has issues. We can't fix this, please try later again.`;
return `❌ Remote server ${error.url.host} has issues. We can't fix this, please try later again.`;
}
return `❌ Request failed with status ${error.status} (${error.statusText}). Request-id: ${ctx.id}.`;
return `❌ Request to ${error.url.host} failed with status ${error.status} (${error.statusText}). Request-id: ${ctx.id}.`;
}
const cause = (error as Error & { cause?: unknown }).cause;

View file

@ -213,10 +213,10 @@ export class Fetcher {
await this.rateLimitedCache.set<true>(url.host, true, retryAfter * 1000);
}
throw new TooManyRequestsError(retryAfter);
throw new TooManyRequestsError(url, retryAfter);
}
throw new HttpError(httpCacheItem.status, httpCacheItem.statusText, httpCacheItem.headers);
throw new HttpError(url, httpCacheItem.status, httpCacheItem.statusText, httpCacheItem.headers);
};
private determineCacheKey(url: URL, init?: CustomRequestInit): string {
@ -308,12 +308,12 @@ export class Fetcher {
return await this.fetchWithTimeout(ctx, url, { ...init, queueLimit: 1 }, ++tryCount);
}
throw new TooManyRequestsError((isRateLimitedRaw.expires as number - Date.now()) / 1000);
throw new TooManyRequestsError(url, (isRateLimitedRaw.expires as number - Date.now()) / 1000);
}
const timeouts = (await this.timeoutsCountCache.get<number>(url.host)) ?? 0;
if (timeouts >= (init?.timeoutsCountThrow ?? this.DEFAULT_TIMEOUTS_COUNT_THROW)) {
throw new TooManyTimeoutsError();
throw new TooManyTimeoutsError(url);
}
let response;
@ -335,7 +335,7 @@ export class Fetcher {
if (error instanceof DOMException && ['AbortError', 'TimeoutError'].includes(error.name)) {
await this.increaseTimeoutsCount(url);
throw new TimeoutError();
throw new TimeoutError(url);
}
if (tryCount < 3) {
@ -393,7 +393,7 @@ export class Fetcher {
let sem = this.semaphores.get(url.host);
if (!sem) {
sem = withTimeout(new Semaphore(queueLimit), queueTimeout, new QueueIsFullError());
sem = withTimeout(new Semaphore(queueLimit), queueTimeout, new QueueIsFullError(url));
this.semaphores.set(url.host, sem);
}

View file

@ -153,7 +153,7 @@ describe('resolve', () => {
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new TooManyRequestsError(10),
error: new TooManyRequestsError(new URL('https://example.com'), 10),
label: 'hoster.com',
sourceId: '',
meta: {
@ -164,7 +164,7 @@ describe('resolve', () => {
url: new URL('https://example.com'),
format: Format.unknown,
isExternal: true,
error: new TooManyTimeoutsError(),
error: new TooManyTimeoutsError(new URL('https://example.com')),
label: 'hoster.com',
sourceId: '',
meta: {
@ -195,7 +195,7 @@ describe('resolve', () => {
url: new URL('https://example2.com'),
format: Format.unknown,
isExternal: true,
error: new TimeoutError(),
error: new TimeoutError(new URL('https://example2.com')),
label: 'hoster.com',
sourceId: '',
meta: {
@ -206,7 +206,7 @@ describe('resolve', () => {
url: new URL('https://example3.com'),
format: Format.unknown,
isExternal: true,
error: new QueueIsFullError(),
error: new QueueIsFullError(new URL('https://example3.com')),
label: 'hoster.com',
sourceId: '',
meta: {
@ -217,7 +217,7 @@ describe('resolve', () => {
url: new URL('https://example4.com'),
format: Format.unknown,
isExternal: true,
error: new HttpError(500, 'Internal Server Error', { 'x-foo': 'bar' }),
error: new HttpError(new URL('https://example4.com'), 500, 'Internal Server Error', { 'x-foo': 'bar' }),
label: 'hoster.com',
sourceId: '',
meta: {
@ -228,7 +228,7 @@ describe('resolve', () => {
url: new URL('https://example5.com'),
format: Format.unknown,
isExternal: true,
error: new HttpError(418, 'I\'m a tea pot', { 'x-foo': 'bar' }),
error: new HttpError(new URL('https://example5.com'), 418, 'I\'m a tea pot', { 'x-foo': 'bar' }),
label: 'hoster.com',
sourceId: '',
meta: {

View file

@ -35,7 +35,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example5.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
❌ Request failed with status 418 (I'm a tea pot). Request-id: test.",
❌ Request to example5.com failed with status 418 (I'm a tea pot). Request-id: test.",
},
{
"behaviorHints": {
@ -44,7 +44,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example4.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
❌ Remote server has issues. We can't fix this, please try later again.",
❌ Remote server example4.com has issues. We can't fix this, please try later again.",
},
{
"behaviorHints": {
@ -53,7 +53,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example3.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
⏳ Request queue is full. Please try again later or consider self-hosting.",
⏳ Request queue for example3.com is full. Please try again later or consider self-hosting.",
},
{
"behaviorHints": {
@ -62,7 +62,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example2.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
🐢 Request timed out.",
🐢 Request to example2.com timed out.",
},
{
"behaviorHints": {
@ -80,7 +80,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
🚦 Too many recent timeouts. Please try again later.",
🚦 Too many recent timeouts when requesting example.com. Please try again later.",
},
{
"behaviorHints": {
@ -89,7 +89,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
🚦 Request was rate-limited. Please try again later or consider self-hosting.",
🚦 Request to example.com was rate-limited. Please try again later or consider self-hosting.",
},
{
"behaviorHints": {
@ -98,7 +98,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
⚠️ Request was blocked. Reason: unknown",
⚠️ Request to example.com was blocked. Reason: unknown",
},
{
"behaviorHints": {
@ -116,7 +116,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
⚠️ Request was blocked. Reason: cloudflare_censor",
⚠️ Request to example.com was blocked. Reason: cloudflare_censor",
},
{
"behaviorHints": {
@ -125,7 +125,7 @@ exports[`resolve adds error info 2`] = `
"externalUrl": "https://example.com/",
"name": "WebStreamr 🇩🇪",
"title": "🔗 hoster.com
⚠️ Request was blocked. Reason: cloudflare_challenge",
⚠️ Request to example.com was blocked. Reason: cloudflare_challenge",
},
{
"behaviorHints": {