feat: 在AppLoader中新增异常处理的注册,以支持异常信息上报

This commit is contained in:
Pan Qiancheng 2025-11-06 15:35:34 +08:00
parent 4c1e55982c
commit d19cdec336
6 changed files with 113 additions and 1 deletions

11
lib/AppLoader.d.ts vendored
View File

@ -7,6 +7,7 @@ import { Namespace } from 'socket.io';
import DataSubscriber from './cluster/DataSubscriber';
import Synchronizer from './Synchronizer';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import { InternalErrorHandler } from './types';
export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
protected dbStore: DbStore<ED, Cxt>;
private aspectDict;
@ -17,6 +18,16 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
private nsSocket?;
private watcherTimerId?;
private scheduledJobs;
private internalErrorHandlers;
/**
*
* @param handler
*/
registerInternalErrorHandler(handler: InternalErrorHandler<ED, Cxt>): void;
/**
*
*/
private publishInternalError;
private requireSth;
protected makeContext(cxtStr?: string, headers?: IncomingHttpHeaders): Promise<Cxt>;
/**

View File

@ -28,6 +28,38 @@ class AppLoader extends types_1.AppLoader {
nsSocket;
watcherTimerId;
scheduledJobs = {};
internalErrorHandlers = new Array();
/**
* 注册一个内部错误处理器
* @param handler 内部错误处理器
*/
registerInternalErrorHandler(handler) {
this.internalErrorHandlers.push(handler);
}
/**
* 发布内部错误事件给注册的处理器
*/
async publishInternalError(type, message, err) {
const errorToPublish = (0, lodash_1.cloneDeep)(err);
let oakException;
if (errorToPublish instanceof types_1.OakException) {
oakException = errorToPublish;
}
await Promise.all(this.internalErrorHandlers.map((handler) => {
return new Promise(async (resolve) => {
try {
const ctx = await this.makeContext();
handler(ctx, type, message, errorToPublish, oakException);
}
catch (e) {
console.error('执行internalErrorHandler时出错', e);
}
finally {
resolve();
}
});
}));
}
requireSth(filePath) {
return (0, requirePrj_1.default)(this.path, filePath, this.externalDependencies);
}
@ -174,6 +206,7 @@ class AppLoader extends types_1.AppLoader {
catch (err) {
console.error(`执行aspect「${name}」出错`, err);
await context.rollback();
this.publishInternalError(`aspect`, `执行aspect「${name}」出错`, err);
if (err instanceof types_1.OakException) {
throw err;
}
@ -358,6 +391,7 @@ class AppLoader extends types_1.AppLoader {
else {
await context.rollback();
}
// 不能在这里publish因为这个方法可能是在timer中调用也可能是在routine中调用
throw err;
}
}
@ -383,6 +417,7 @@ class AppLoader extends types_1.AppLoader {
}
catch (err) {
console.error(`执行watcher【${watcher.name}】失败,耗时【${Date.now() - start}】,结果是:`, err);
await this.publishInternalError(`watcher`, `执行watcher【${watcher.name}】失败`, err);
}
};
const doWatchers = async () => {
@ -401,6 +436,7 @@ class AppLoader extends types_1.AppLoader {
}
catch (err) {
console.error(`执行了checkpoint发生错误`, err);
await this.publishInternalError(`checkpoint`, `执行checkpoint发生错误`, err);
}
this.watcherTimerId = setTimeout(() => doWatchers(), 120000);
};
@ -425,6 +461,7 @@ class AppLoader extends types_1.AppLoader {
}
catch (err) {
console.error(`定时器【${name}】执行失败,耗时${Date.now() - start}毫秒】,错误是`, err);
this.publishInternalError(`timer`, `定时器【${name}】执行失败`, err);
}
}
else {
@ -444,6 +481,7 @@ class AppLoader extends types_1.AppLoader {
else {
await context.rollback();
}
this.publishInternalError(`timer`, `定时器【${name}】执行失败`, err);
}
}
});

6
lib/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
import { BaseEntityDict } from "oak-domain";
import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
import { EntityDict, OakException } from "oak-domain/lib/types";
export type InternalErrorType = 'aspect' | 'trigger' | 'watcher' | 'timer' | 'checkpoint';
export type InternalErrorHandler<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> = (ctx: Cxt, type: InternalErrorType, message: string, err: Error, oakException?: OakException<ED>) => Promise<void>;
export type ExceptionPublisher = (type: string, message: string, err: any) => Promise<void>;

2
lib/types/index.js Normal file
View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -22,6 +22,7 @@ import Synchronizer from './Synchronizer';
import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore';
import domainI18nData from 'oak-domain/lib/data/i18n';
import requireSth from './utils/requirePrj';
import { InternalErrorHandler, InternalErrorType } from './types';
export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
protected dbStore: DbStore<ED, Cxt>;
@ -33,6 +34,40 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
private nsSocket?: Namespace;
private watcherTimerId?: NodeJS.Timeout;
private scheduledJobs: Record<string, Job> = {};
private internalErrorHandlers = new Array<InternalErrorHandler<ED, Cxt>>();
/**
*
* @param handler
*/
public registerInternalErrorHandler(handler: InternalErrorHandler<ED, Cxt>) {
this.internalErrorHandlers.push(handler);
}
/**
*
*/
private async publishInternalError(type: InternalErrorType, message: string, err: any) {
const errorToPublish = cloneDeep(err);
let oakException: OakException<ED> | undefined;
if (errorToPublish instanceof OakException) {
oakException = errorToPublish;
}
await Promise.all(this.internalErrorHandlers.map(
(handler) => {
return new Promise<void>(async (resolve) => {
try {
const ctx = await this.makeContext();
handler(ctx, type, message, errorToPublish, oakException);
} catch (e) {
console.error('执行internalErrorHandler时出错', e);
} finally {
resolve();
}
});
}
));
}
private requireSth(filePath: string): any {
return requireSth(this.path, filePath, this.externalDependencies);
@ -76,7 +111,14 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
this.externalDependencies = depGraph.ascOrder;
const { authDeduceRelationMap, selectFreeEntities, updateFreeDict } = this.requireSth('lib/configuration/relation')!;
this.aspectDict = Object.assign({}, generalAspectDict, this.requireSth('lib/aspects/index')!);
this.dbStore = new DbStore<ED, Cxt>(storageSchema, () => this.contextBuilder(this.dbStore), dbConfig, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
this.dbStore = new DbStore<ED, Cxt>(
storageSchema,
() => this.contextBuilder(this.dbStore),
dbConfig,
authDeduceRelationMap,
selectFreeEntities,
updateFreeDict,
);
if (nsSubscribe) {
this.dataSubscriber = new DataSubscriber(nsSubscribe, nsServer);
}
@ -223,6 +265,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
catch (err) {
console.error(`执行aspect「${name}」出错`, err);
await context.rollback();
this.publishInternalError(`aspect`, `执行aspect「${name}」出错`, err)
if (err instanceof OakException) {
throw err;
}
@ -427,6 +470,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
else {
await context.rollback();
}
// 不能在这里publish因为这个方法可能是在timer中调用也可能是在routine中调用
throw err;
}
}
@ -457,6 +501,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
}
catch (err) {
console.error(`执行watcher【${watcher.name}】失败,耗时【${Date.now() - start}】,结果是:`, err);
await this.publishInternalError(`watcher`, `执行watcher【${watcher.name}】失败`, err);
}
};
const doWatchers = async () => {
@ -476,6 +521,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
}
catch (err) {
console.error(`执行了checkpoint发生错误`, err);
await this.publishInternalError(`checkpoint`, `执行checkpoint发生错误`, err);
}
this.watcherTimerId = setTimeout(() => doWatchers(), 120000);
@ -504,6 +550,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
}
catch (err) {
console.error(`定时器【${name}】执行失败,耗时${Date.now() - start}毫秒】,错误是`, err);
this.publishInternalError(`timer`, `定时器【${name}】执行失败`, err);
}
}
else {
@ -523,6 +570,7 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
else {
await context.rollback();
}
this.publishInternalError(`timer`, `定时器【${name}】执行失败`, err);
}
}
})

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

@ -0,0 +1,7 @@
import { BaseEntityDict } from "oak-domain";
import { AsyncContext } from "oak-domain/lib/store/AsyncRowStore";
import { EntityDict, OakException } from "oak-domain/lib/types";
export type InternalErrorType = 'aspect' | 'trigger' | 'watcher' | 'timer' | 'checkpoint'
export type InternalErrorHandler<ED extends EntityDict & BaseEntityDict, Cxt extends AsyncContext<ED>> = (ctx: Cxt, type: InternalErrorType, message:string, err: Error, oakException?: OakException<ED>, ) => Promise<void>;
export type ExceptionPublisher = (type: string, message: string, err: any) => Promise<void>;