From 68a99bf4d957a7e2787ec56e90a07bd867f45a54 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Fri, 1 Mar 2024 21:42:51 +0800 Subject: [PATCH] feat: logger middleware --- lib/app.ts | 20 ++++++++++++-------- lib/middleware/header.ts | 5 ----- lib/middleware/logger.ts | 40 ++++++++++++++++++++++++++++++++++++++++ lib/utils/helpers.ts | 17 +++++++++++++++++ 4 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 lib/middleware/logger.ts diff --git a/lib/app.ts b/lib/app.ts index be47a9241..bb92327a0 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -3,6 +3,7 @@ import '@/utils/request-wrapper'; import { Hono } from 'hono'; import { compress } from 'hono/compress'; +import mLogger from '@/middleware/logger'; import cache from '@/middleware/cache'; import template from '@/middleware/template'; import sentry from '@/middleware/sentry'; @@ -11,6 +12,7 @@ import debug from '@/middleware/debug'; import header from '@/middleware/header'; import antiHotlink from '@/middleware/anti-hotlink'; import parameter from '@/middleware/parameter'; + import logger from '@/utils/logger'; import routes from '@/routes'; @@ -23,14 +25,16 @@ process.on('uncaughtException', (e) => { const app = new Hono(); app.use(compress()); -app.use('*', sentry); -app.use('*', accessControl); -app.use('*', debug); -app.use('*', template); -app.use('*', header); -app.use('*', antiHotlink); -app.use('*', parameter); -app.use('*', cache); + +app.use(mLogger); +app.use(sentry); +app.use(accessControl); +app.use(debug); +app.use(template); +app.use(header); +app.use(antiHotlink); +app.use(parameter); +app.use(cache); routes(app); diff --git a/lib/middleware/header.ts b/lib/middleware/header.ts index fc049711a..f8badd785 100644 --- a/lib/middleware/header.ts +++ b/lib/middleware/header.ts @@ -1,7 +1,5 @@ -import { getIp } from '@/utils/helpers'; import { MiddlewareHandler } from 'hono'; import etagCalculate from 'etag'; -import logger from '@/utils/logger'; import { config } from '@/config'; import { Data } from '@/types'; @@ -20,9 +18,6 @@ function etagMatches(etag: string, ifNoneMatch: string | null) { } const middleware: MiddlewareHandler = async (ctx, next) => { - const ip = getIp(ctx); - logger.info(`${ctx.req.url}, user IP: ${ip}`); - for (const key in headers) { ctx.header(key, headers[key]); } diff --git a/lib/middleware/logger.ts b/lib/middleware/logger.ts new file mode 100644 index 000000000..16431944d --- /dev/null +++ b/lib/middleware/logger.ts @@ -0,0 +1,40 @@ +import { MiddlewareHandler } from 'hono'; +import logger from '@/utils/logger'; +import { getIp, getPath, time } from '@/utils/helpers'; + +enum LogPrefix { + Outgoing = '-->', + Incoming = '<--', + Error = 'xxx', +} + +const colorStatus = (status: number) => { + const out: { [key: string]: string } = { + 7: `\u001B[35m${status}\u001B[0m`, + 5: `\u001B[31m${status}\u001B[0m`, + 4: `\u001B[33m${status}\u001B[0m`, + 3: `\u001B[36m${status}\u001B[0m`, + 2: `\u001B[32m${status}\u001B[0m`, + 1: `\u001B[32m${status}\u001B[0m`, + 0: `\u001B[33m${status}\u001B[0m`, + }; + + const calculateStatus = Math.trunc(status / 100); + + return out[calculateStatus]; +}; + +const middleware: MiddlewareHandler = async (ctx, next) => { + const { method, raw } = ctx.req; + const path = getPath(raw); + + logger.info(`${LogPrefix.Incoming} ${method} ${path} from ${getIp(ctx)}`); + + const start = Date.now(); + + await next(); + + logger.info(`${LogPrefix.Outgoing} ${method} ${path} ${colorStatus(ctx.res.status)} ${time(start)}`); +}; + +export default middleware; diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index aeed986fa..285e094ea 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -13,3 +13,20 @@ export const getRouteNameFromPath = (path: string) => { }; export const getIp = (ctx: Context) => (config.nodeName === 'mock' && ctx.req.header('X-Mock-IP') ? ctx.req.header('X-Mock-IP') : ipware.getClientIP(ctx.req.raw)?.ip); + +export const getPath = (request: Request): string => { + // Optimized: RegExp is faster than indexOf() + slice() + const match = request.url.match(/^https?:\/\/[^/]+(\/[^?]*)/); + return match ? match[1] : ''; +}; + +const humanize = (times: string[]) => { + const [delimiter, separator] = [',', '.']; + const orderTimes = times.map((v) => v.replaceAll(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + delimiter)); + return orderTimes.join(separator); +}; + +export const time = (start: number) => { + const delta = Date.now() - start; + return humanize([delta < 1000 ? delta + 'ms' : Math.round(delta / 1000) + 's']); +};