feat: 支持在configuration中新建errors.ts来对项目的console.error做额外的polyfill处理

This commit is contained in:
Pan Qiancheng 2025-10-16 10:59:12 +08:00
parent a2cca9dd46
commit 893b1b04cb
3 changed files with 150 additions and 0 deletions

View File

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

View File

@ -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<ED extends EntityDict & BaseEntityDict, FrontCxt e
omitTimers?: boolean,
routine?: (context: AsyncContext<ED>) => Promise<void>,
): Promise<(() => Promise<any>) | any> {
const errorHandler: ErrorHandler<ED> = require(join(
path,
'lib',
'configuration',
'errors'
)).default;
const serverConfiguration: ServerConfiguration = require(join(
path,
'lib',
@ -204,6 +214,17 @@ export async function startup<ED extends EntityDict & BaseEntityDict, FrontCxt e
return result;
}
if (errorHandler && typeof errorHandler === 'function') {
polyfillConsole("startup", true, (props) => {
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<ED extends EntityDict & BaseEntityDict, FrontCxt e
await httpServer.close();
await koa.removeAllListeners();
await appLoader.unmount();
removePolyfill("startup");
}
return shutdown

5
src/types/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { EntityDict } from "oak-domain/lib/types";
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
export type ErrorHandler<ED extends EntityDict & BaseEntityDict> = ( caller: NodeJS.CallSite | null, args: any[], ctx: AsyncContext<ED>) => Promise<void>;