fix: error handler and debug info
This commit is contained in:
parent
8e0154552c
commit
e7e3f689bd
|
|
@ -1,3 +1,65 @@
|
|||
import { type ErrorHandler } from 'hono';
|
||||
import _RequestInProgressError from './RequestInProgress';
|
||||
import { getDebugInfo, setDebugInfo } from '@/utils/debug-info';
|
||||
import { config } from '@/config';
|
||||
import Sentry from '@sentry/node';
|
||||
import logger from '@/utils/logger';
|
||||
import art from 'art-template';
|
||||
import * as path from 'node:path';
|
||||
import gitHash from '@/utils/git-hash';
|
||||
|
||||
export const RequestInProgressError = _RequestInProgressError;
|
||||
|
||||
export const errorHandler: ErrorHandler = (error, ctx) => {
|
||||
let message = '';
|
||||
if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError')) {
|
||||
message = `${error.message}: target website might be blocking our access, you can <a href="https://docs.rsshub.app/install/">host your own RSSHub instance</a> for a better usability.`;
|
||||
} else if (error instanceof Error) {
|
||||
message = process.env.NODE_ENV === 'production' ? error.message : (error.stack || error.message);
|
||||
}
|
||||
|
||||
const debug = getDebugInfo();
|
||||
if (ctx.res.headers.get('X-Koa-Redis-Cache') || ctx.res.headers.get('X-Koa-Memory-Cache')) {
|
||||
debug.hitCache++;
|
||||
setDebugInfo(debug);
|
||||
}
|
||||
|
||||
if (config.sentry.dsn) {
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setTag('name', ctx.req.path.split('/')[1]);
|
||||
Sentry.captureException(error);
|
||||
});
|
||||
}
|
||||
|
||||
logger.error(`Error in ${ctx.req.path}: ${message}`);
|
||||
|
||||
if (config.isPackage) {
|
||||
return ctx.json({
|
||||
error: {
|
||||
message: error.message ?? error,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
ctx.header('Content-Type', 'text/html; charset=UTF-8');
|
||||
|
||||
if (error instanceof RequestInProgressError) {
|
||||
ctx.status(503);
|
||||
message = error.message;
|
||||
ctx.set('Cache-Control', `public, max-age=${config.cache.requestTimeout}`);
|
||||
} else if (ctx.res.status === 403) {
|
||||
message = error.message;
|
||||
} else {
|
||||
ctx.status(404);
|
||||
}
|
||||
|
||||
const requestPath = ctx.req.path;
|
||||
|
||||
return ctx.body(art(path.resolve(__dirname, '../views/error.art'), {
|
||||
requestPath,
|
||||
message,
|
||||
errorPath: ctx.req.path,
|
||||
nodeVersion: process.version,
|
||||
gitHash,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import logger from '@/utils/logger'
|
|||
import routes from '@/routes'
|
||||
import index from '@/v3/index'
|
||||
import { config } from '@/config'
|
||||
import { errorHandler } from '@/errors'
|
||||
|
||||
process.on('uncaughtException', (e) => {
|
||||
logger.error('uncaughtException: ' + e);
|
||||
|
|
@ -46,7 +47,7 @@ for (const name in routes) {
|
|||
|
||||
app.get('/', index)
|
||||
|
||||
console.log(app)
|
||||
app.onError(errorHandler)
|
||||
|
||||
const port = config.connect.port
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +1,26 @@
|
|||
import { MiddlewareHandler } from "hono";
|
||||
import { getRouteNameFromPath } from '@/utils/helpers';
|
||||
|
||||
const debug = {
|
||||
hitCache: 0,
|
||||
request: 0,
|
||||
etag: 0,
|
||||
paths: [],
|
||||
routes: [],
|
||||
errorPaths: [],
|
||||
errorRoutes: [],
|
||||
}
|
||||
import { getDebugInfo, setDebugInfo } from "@/utils/debug-info";
|
||||
|
||||
const middleware: MiddlewareHandler = async (ctx, next) => {
|
||||
if (!debug.paths[ctx.req.path]) {
|
||||
debug.paths[ctx.req.path] = 0;
|
||||
{
|
||||
const debug = getDebugInfo();
|
||||
debug.request++;
|
||||
setDebugInfo(debug);
|
||||
}
|
||||
debug.paths[ctx.req.path]++;
|
||||
|
||||
debug.request++;
|
||||
|
||||
await next();
|
||||
|
||||
const routeName = getRouteNameFromPath(ctx.req.path);
|
||||
if (routeName) {
|
||||
if (!debug.routes[routeName]) {
|
||||
debug.routes[routeName] = 0;
|
||||
{
|
||||
const debug = getDebugInfo();
|
||||
if (ctx.res.headers.get('X-Koa-Redis-Cache') || ctx.res.headers.get('X-Koa-Memory-Cache')) {
|
||||
debug.hitCache++;
|
||||
}
|
||||
debug.routes[routeName]++;
|
||||
}
|
||||
|
||||
if (ctx.res.headers.get('X-Koa-Redis-Cache') || ctx.res.headers.get('X-Koa-Memory-Cache')) {
|
||||
debug.hitCache++;
|
||||
}
|
||||
|
||||
if (ctx.res.status === 304) {
|
||||
debug.etag++;
|
||||
if (ctx.res.status === 304) {
|
||||
debug.etag++;
|
||||
}
|
||||
setDebugInfo(debug);
|
||||
}
|
||||
};
|
||||
|
||||
export default middleware;
|
||||
|
||||
export const getDebugInfo = () => debug
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
import { MiddlewareHandler } from "hono";
|
||||
import logger from "@/utils/logger";
|
||||
import { config } from "@/config";
|
||||
import art from 'art-template';
|
||||
import * as path from 'node:path';
|
||||
import { RequestInProgressError } from '@/errors';
|
||||
import Sentry from '@sentry/node';
|
||||
import { getRouteNameFromPath } from "@/utils/helpers";
|
||||
import gitRevSync from 'git-rev-sync';
|
||||
|
||||
let gitHash;
|
||||
|
||||
if (config.sentry.dsn) {
|
||||
Sentry.init({
|
||||
|
|
@ -19,74 +13,15 @@ if (config.sentry.dsn) {
|
|||
logger.info('Sentry inited.');
|
||||
}
|
||||
|
||||
try {
|
||||
gitHash = gitRevSync.short();
|
||||
} catch {
|
||||
gitHash = (process.env.HEROKU_SLUG_COMMIT && process.env.HEROKU_SLUG_COMMIT.slice(0, 7)) || (process.env.VERCEL_GIT_COMMIT_SHA && process.env.VERCEL_GIT_COMMIT_SHA.slice(0, 7)) || 'unknown';
|
||||
}
|
||||
|
||||
const middleware: MiddlewareHandler = async (ctx, next) => {
|
||||
try {
|
||||
const time = Date.now();
|
||||
await next();
|
||||
if (config.sentry.dsn && Date.now() - time >= config.sentry.routeTimeout) {
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setTag('name', getRouteNameFromPath(ctx.req.path));
|
||||
Sentry.captureException(new Error('Route Timeout'));
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
let message = error;
|
||||
if (error.name && (error.name === 'HTTPError' || error.name === 'RequestError')) {
|
||||
message = `${error.message}: target website might be blocking our access, you can <a href="https://docs.rsshub.app/install/">host your own RSSHub instance</a> for a better usability.`;
|
||||
} else if (error instanceof Error) {
|
||||
message = process.env.NODE_ENV === 'production' ? error.message : error.stack;
|
||||
}
|
||||
|
||||
logger.error(`Error in ${ctx.req.path}: ${message}`);
|
||||
|
||||
if (config.isPackage) {
|
||||
ctx.json({
|
||||
error: {
|
||||
message: error.message ?? error,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
ctx.header('Content-Type', 'text/html; charset=UTF-8');
|
||||
|
||||
if (error instanceof RequestInProgressError) {
|
||||
ctx.status(503);
|
||||
message = error.message;
|
||||
ctx.set('Cache-Control', `public, max-age=${config.cache.requestTimeout}`);
|
||||
} else if (ctx.res.status === 403) {
|
||||
message = error.message;
|
||||
} else {
|
||||
ctx.status(404);
|
||||
}
|
||||
|
||||
const requestPath = ctx.req.path;
|
||||
|
||||
ctx.body = art(path.resolve(__dirname, '../views/error.art'), {
|
||||
requestPath,
|
||||
message,
|
||||
errorPath: ctx.req.path,
|
||||
nodeVersion: process.version,
|
||||
gitHash,
|
||||
});
|
||||
}
|
||||
|
||||
const debug = ctx.get('debug');
|
||||
if (ctx.res.headers.get('X-Koa-Redis-Cache') || ctx.res.headers.get('X-Koa-Memory-Cache')) {
|
||||
debug.hitCache++;
|
||||
}
|
||||
|
||||
if (config.sentry.dsn) {
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setTag('name', ctx.req.path.split('/')[1]);
|
||||
Sentry.captureException(error);
|
||||
});
|
||||
}
|
||||
const time = Date.now();
|
||||
await next();
|
||||
if (config.sentry.dsn && Date.now() - time >= config.sentry.routeTimeout) {
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setTag('name', getRouteNameFromPath(ctx.req.path));
|
||||
Sentry.captureException(new Error('Route Timeout'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default middleware;
|
||||
export default middleware;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { directoryImport } from '@/utils/directory-import';
|
||||
import type { Context, Handler, Hono } from 'hono'
|
||||
import type { Handler } from 'hono'
|
||||
|
||||
type Root = {
|
||||
get: (path: string, handler: Handler) => void;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
const debug = {
|
||||
hitCache: 0,
|
||||
request: 0,
|
||||
etag: 0,
|
||||
}
|
||||
|
||||
export const getDebugInfo = () => debug
|
||||
export const setDebugInfo = (info: typeof debug) => Object.assign(debug, info)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import gitRevSync from 'git-rev-sync';
|
||||
|
||||
let gitHash = process.env.HEROKU_SLUG_COMMIT?.slice(0, 7) || process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7)
|
||||
if (!gitHash) {
|
||||
try {
|
||||
gitHash = gitRevSync.short();
|
||||
} catch {
|
||||
gitHash = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export default gitHash;
|
||||
|
|
@ -2,17 +2,8 @@ import type { Handler } from 'hono';
|
|||
import { config } from '@/config';
|
||||
import art from 'art-template';
|
||||
import * as path from 'node:path';
|
||||
import gitRevSync from 'git-rev-sync';
|
||||
import { getDebugInfo } from '@/middleware/debug';
|
||||
|
||||
let gitHash = process.env.HEROKU_SLUG_COMMIT?.slice(0, 7) || process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7)
|
||||
if (!gitHash) {
|
||||
try {
|
||||
gitHash = gitRevSync.short();
|
||||
} catch {
|
||||
gitHash = 'unknown';
|
||||
}
|
||||
}
|
||||
import gitHash from '@/utils/git-hash';
|
||||
import { getDebugInfo } from '@/utils/debug-info';
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
|
|
@ -21,28 +12,6 @@ const handler: Handler = (ctx) => {
|
|||
ctx.header('Cache-Control', 'no-cache')
|
||||
|
||||
const debug = getDebugInfo();
|
||||
const routes = Object.keys(debug.routes).sort((a, b) => debug.routes[b] - debug.routes[a]);
|
||||
const hotRoutes = routes.slice(0, 30);
|
||||
const hotRoutesValue = hotRoutes.map((item) => `${debug.routes[item]} ${item}<br>`).join('');
|
||||
|
||||
const paths = Object.keys(debug.paths).sort((a, b) => debug.paths[b] - debug.paths[a]);
|
||||
const hotPaths = paths.slice(0, 30);
|
||||
|
||||
const hotPathsValue = hotPaths.map((item) => `${debug.paths[item]} ${item}<br>`).join('');
|
||||
|
||||
let hotErrorRoutesValue = '';
|
||||
if (debug.errorRoutes) {
|
||||
const errorRoutes = Object.keys(debug.errorRoutes).sort((a, b) => debug.errorRoutes[b] - debug.errorRoutes[a]);
|
||||
const hotErrorRoutes = errorRoutes.slice(0, 30);
|
||||
hotErrorRoutesValue = hotErrorRoutes.map((item) => `${debug.errorRoutes[item]} ${item}<br>`).join('');
|
||||
}
|
||||
|
||||
let hotErrorPathsValue = '';
|
||||
if (debug.errorPaths) {
|
||||
const errorPaths = Object.keys(debug.errorPaths).sort((a, b) => debug.errorPaths[b] - debug.errorPaths[a]);
|
||||
const hotErrorPaths = errorPaths.slice(0, 30);
|
||||
hotErrorPathsValue = hotErrorPaths.map((item) => `${debug.errorPaths[item]} ${item}<br>`).join('');
|
||||
}
|
||||
|
||||
const showDebug = !config.debugInfo || config.debugInfo === 'false' ? false : config.debugInfo === 'true' || config.debugInfo === ctx.req.query('debug');
|
||||
const { disallowRobot, nodeName } = config;
|
||||
|
|
@ -83,22 +52,6 @@ const handler: Handler = (ctx) => {
|
|||
name: 'Run Time',
|
||||
value: (duration / 3_600_000).toFixed(2) + ' hour(s)',
|
||||
},
|
||||
{
|
||||
name: 'Hot Routes',
|
||||
value: hotRoutesValue,
|
||||
},
|
||||
{
|
||||
name: 'Hot Paths',
|
||||
value: hotPathsValue,
|
||||
},
|
||||
{
|
||||
name: 'Hot Error Routes',
|
||||
value: hotErrorRoutesValue,
|
||||
},
|
||||
{
|
||||
name: 'Hot Error Paths',
|
||||
value: hotErrorPathsValue,
|
||||
},
|
||||
],
|
||||
}));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<body>
|
||||
<div class="content">
|
||||
<p>
|
||||
<img src="/logo.png" alt="RSSHub" width="120" loading="lazy" />
|
||||
<img src="https://i.imgur.com/KmgWkHm.png" alt="RSSHub" width="120" loading="lazy" />
|
||||
</p>
|
||||
|
||||
<h1>Looks like something went wrong</h1>
|
||||
|
|
|
|||
Loading…
Reference in New Issue