fix: error handler and debug info

This commit is contained in:
DIYgod 2024-01-21 15:41:42 +08:00
parent 8e0154552c
commit e7e3f689bd
No known key found for this signature in database
9 changed files with 109 additions and 155 deletions

View File

@ -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,
}));
}
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

8
lib/utils/debug-info.ts Normal file
View File

@ -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)

12
lib/utils/git-hash.ts Normal file
View File

@ -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;

View File

@ -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,
},
],
}));
};

View File

@ -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>