From e7e3f689bd541c0948aca1d36c9426a1b2f836bc Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sun, 21 Jan 2024 15:41:42 +0800 Subject: [PATCH] fix: error handler and debug info --- lib/errors/index.ts | 62 ++++++++++++++++++++++++++++++ lib/index.ts | 3 +- lib/middleware/debug.ts | 43 +++++++-------------- lib/middleware/onerror.ts | 81 ++++----------------------------------- lib/routes.ts | 2 +- lib/utils/debug-info.ts | 8 ++++ lib/utils/git-hash.ts | 12 ++++++ lib/v3/index.ts | 51 +----------------------- lib/views/error.art | 2 +- 9 files changed, 109 insertions(+), 155 deletions(-) create mode 100644 lib/utils/debug-info.ts create mode 100644 lib/utils/git-hash.ts diff --git a/lib/errors/index.ts b/lib/errors/index.ts index 97027ed4d..a400516a1 100644 --- a/lib/errors/index.ts +++ b/lib/errors/index.ts @@ -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 host your own RSSHub instance 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, + })); + } +} diff --git a/lib/index.ts b/lib/index.ts index 1abea49b7..3691ec74a 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -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 diff --git a/lib/middleware/debug.ts b/lib/middleware/debug.ts index 71e914987..ef6188e29 100644 --- a/lib/middleware/debug.ts +++ b/lib/middleware/debug.ts @@ -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 diff --git a/lib/middleware/onerror.ts b/lib/middleware/onerror.ts index a332f1ea3..6aa6a3def 100644 --- a/lib/middleware/onerror.ts +++ b/lib/middleware/onerror.ts @@ -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 host your own RSSHub instance 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; \ No newline at end of file +export default middleware; diff --git a/lib/routes.ts b/lib/routes.ts index 3a8388bb6..ade7d80f3 100644 --- a/lib/routes.ts +++ b/lib/routes.ts @@ -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; diff --git a/lib/utils/debug-info.ts b/lib/utils/debug-info.ts new file mode 100644 index 000000000..d54e5e70c --- /dev/null +++ b/lib/utils/debug-info.ts @@ -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) diff --git a/lib/utils/git-hash.ts b/lib/utils/git-hash.ts new file mode 100644 index 000000000..2eafc589d --- /dev/null +++ b/lib/utils/git-hash.ts @@ -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; diff --git a/lib/v3/index.ts b/lib/v3/index.ts index e606d7dd0..6c3981310 100644 --- a/lib/v3/index.ts +++ b/lib/v3/index.ts @@ -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}
`).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}
`).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}
`).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}
`).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, - }, ], })); }; diff --git a/lib/views/error.art b/lib/views/error.art index e128d63fc..8c83de401 100644 --- a/lib/views/error.art +++ b/lib/views/error.art @@ -36,7 +36,7 @@

- RSSHub + RSSHub

Looks like something went wrong