import * as Sentry from '@sentry/node'; import type { ErrorHandler, NotFoundHandler } from 'hono'; import { routePath } from 'hono/route'; import { config } from '@/config'; import { getDebugInfo, setDebugInfo } from '@/utils/debug-info'; import logger from '@/utils/logger'; import { requestMetric } from '@/utils/otel'; import Error from '@/views/error'; import NotFoundError from './types/not-found'; export const errorHandler: ErrorHandler = (error, ctx) => { const requestPath = ctx.req.path; const matchedRoute = routePath(ctx); const hasMatchedRoute = matchedRoute !== '/*'; const debug = getDebugInfo(); try { if (ctx.res.headers.get('RSSHub-Cache-Status')) { debug.hitCache++; } } catch { // ignore } debug.error++; if (!debug.errorPaths[requestPath]) { debug.errorPaths[requestPath] = 0; } debug.errorPaths[requestPath]++; if (!debug.errorRoutes[matchedRoute] && hasMatchedRoute) { debug.errorRoutes[matchedRoute] = 0; } hasMatchedRoute && debug.errorRoutes[matchedRoute]++; setDebugInfo(debug); if (config.sentry.dsn) { Sentry.withScope((scope) => { scope.setTag('name', requestPath.split('/')[1]); Sentry.captureException(error); }); } let errorMessage = (process.env.NODE_ENV || process.env.VERCEL_ENV) === 'production' ? error.message : error.stack || error.message; switch (error.constructor.name) { case 'HTTPError': case 'RequestError': case 'FetchError': ctx.status(503); break; case 'RequestInProgressError': ctx.header('Cache-Control', `public, max-age=${config.requestTimeout / 1000}`); ctx.status(503); break; case 'RejectError': ctx.status(403); break; case 'NotFoundError': ctx.status(404); errorMessage += 'The route does not exist or has been deleted.'; break; default: ctx.status(503); break; } const message = `${error.name}: ${errorMessage}`; logger.error(`Error in ${requestPath}: ${message}`); requestMetric.error({ path: matchedRoute, method: ctx.req.method, status: ctx.res.status }); return config.isPackage || ctx.req.query('format') === 'json' ? ctx.json({ error: { message: error.message ?? error, }, }) : ctx.html(); }; export const notFoundHandler: NotFoundHandler = (ctx) => errorHandler(new NotFoundError(), ctx);