diff --git a/src/server/polyfill.ts b/src/server/polyfill.ts index 2e589e8..dce309b 100644 --- a/src/server/polyfill.ts +++ b/src/server/polyfill.ts @@ -14,3 +14,125 @@ async function generateNewId(option?: GenerateIdOption) { Object.assign(global, { generateNewId, }); + +export type LogFormatterProp = { + level: "info" | "warn" | "error"; + caller: NodeJS.CallSite | null; + args: any[]; +}; + +export type LogFormatter = (props: LogFormatterProp) => any[]; + +type PolyfillItem = { + id: string; + trace: boolean; + formatter?: LogFormatter; +}; + +// 存储所有的 polyfill 配置栈 +const polyfillStack: PolyfillItem[] = []; +const originalConsoleMethods: { + info?: typeof console.info; + warn?: typeof console.warn; + error?: typeof console.error; +} = {}; + +// 获取调用堆栈信息的工厂函数 +const createGetCallerInfo = (trace: boolean) => (): NodeJS.CallSite | null => { + if (!trace) { + return null; + } + const originalFunc = Error.prepareStackTrace; + let callerInfo: NodeJS.CallSite | null = null; + try { + const err = new Error(); + Error.prepareStackTrace = (err, stack) => stack; + const stack = err.stack as unknown as NodeJS.CallSite[]; + const currentFile = stack[0].getFileName(); + + for (let i = 1; i < stack.length; i++) { + const callSite = stack[i]; + if (currentFile !== callSite.getFileName()) { + callerInfo = callSite; + break; + } + } + } catch (e) { + console.error(e); + } + Error.prepareStackTrace = originalFunc; + return callerInfo; +}; + +export const polyfillConsole = (id: string, trace: boolean, formatter?: LogFormatter) => { + // 检查是否已经添加过相同 id 的 polyfill + if (polyfillStack.some(item => item.id === id)) { + return; + } + + // 第一次调用时保存原始方法 + if (polyfillStack.length === 0) { + originalConsoleMethods.info = console.info; + originalConsoleMethods.warn = console.warn; + originalConsoleMethods.error = console.error; + } + + // 添加新的 polyfill 到栈顶 + polyfillStack.push({ id, trace, formatter }); + + // 重新设置 console 方法 + ["info", "warn", "error"].forEach((level: string) => { + const levelStr = level as "info" | "warn" | "error"; + const originalFunc = originalConsoleMethods[levelStr]!; + + console[levelStr] = function (...args) { + let processedArgs = args; + + // 从后往前执行所有 formatter + for (let i = polyfillStack.length - 1; i >= 0; i--) { + const item = polyfillStack[i]; + if (item.formatter) { + const getCallerInfo = createGetCallerInfo(item.trace); + processedArgs = item.formatter({ + level: levelStr, + caller: getCallerInfo(), + args: processedArgs, + }); + } + } + + originalFunc(...processedArgs); + }; + }); + + console.info(`new console polyfill added: ${id}`); +}; + +// 可选:提供移除 polyfill 的功能 +export const removePolyfill = (id: string) => { + const index = polyfillStack.findIndex(item => item.id === id); + if (index === -1) { + return; + } + + polyfillStack.splice(index, 1); + + // 如果栈为空,恢复原始方法 + if (polyfillStack.length === 0) { + console.info = originalConsoleMethods.info!; + console.warn = originalConsoleMethods.warn!; + console.error = originalConsoleMethods.error!; + } else { + // 否则重新应用所有 polyfill + const tempStack = [...polyfillStack]; + polyfillStack.length = 0; + Object.keys(originalConsoleMethods).forEach(level => { + const levelStr = level as "info" | "warn" | "error"; + console[levelStr] = originalConsoleMethods[levelStr]!; + }); + tempStack.forEach(item => { + polyfillStack.length = 0; // 清空以便重新添加 + polyfillConsole(item.id, item.trace, item.formatter); + }); + } +}; \ No newline at end of file diff --git a/src/server/start.ts b/src/server/start.ts index fb8f858..6911d9d 100644 --- a/src/server/start.ts +++ b/src/server/start.ts @@ -25,6 +25,8 @@ import mount from 'koa-mount'; import chalk from 'chalk'; import { checkNodeVersion, randomString } from '../utils'; import bcrypt from 'bcryptjs'; +import { LogFormatter, polyfillConsole, removePolyfill } from './polyfill'; +import { ErrorHandler } from '../types'; checkNodeVersion() @@ -54,6 +56,14 @@ export async function startup) => Promise, ): Promise<(() => Promise) | any> { + + const errorHandler: ErrorHandler = require(join( + path, + 'lib', + 'configuration', + 'errors' + )).default; + const serverConfiguration: ServerConfiguration = require(join( path, 'lib', @@ -204,6 +214,17 @@ export async function startup { + if (props.level === "error") { + appLoader.execRoutine(async (ctx) => { + await errorHandler( props.caller, props.args, ctx); + }); + } + return props.args; + }); + } + // 否则启动服务器模式 koa.use(async (ctx, next) => { try { @@ -451,6 +472,8 @@ export async function startup = ( caller: NodeJS.CallSite | null, args: any[], ctx: AsyncContext) => Promise; \ No newline at end of file