103 lines
4.0 KiB
TypeScript
103 lines
4.0 KiB
TypeScript
import { _decorator, Component, log, sys } from 'cc';
|
||
import { uploadError } from '../main/comm';
|
||
|
||
|
||
export class ErrorManager {
|
||
private static _instance: ErrorManager = null;
|
||
// 用于存储已上报的错误指纹
|
||
private reportedErrors: Set<string> = new Set();
|
||
|
||
public static get instance(): ErrorManager {
|
||
if (!this._instance) {
|
||
this._instance = new ErrorManager();
|
||
}
|
||
return this._instance;
|
||
}
|
||
|
||
private constructor() {
|
||
this.initialize();
|
||
}
|
||
|
||
// 放在 ErrorManager.initialize() 里
|
||
private initialize(): void {
|
||
if ((window as any).__rp_err_init__) return;
|
||
(window as any).__rp_err_init__ = true;
|
||
|
||
// 1) JS 运行时 & 资源加载错误
|
||
window.addEventListener('error', (event: any) => {
|
||
// 资源错误(img/script/link)的 event 没有 error 对象
|
||
if (event && event.target && (event.target.src || event.target.href)) {
|
||
const el = event.target;
|
||
const url = el.src || el.href;
|
||
this.reportError(`resource error: ${el.tagName} ${url}`);
|
||
return;
|
||
}
|
||
const err: any = event?.error || {};
|
||
const msg = `error: ${event?.message}\nfrom: ${event?.filename}\npos: ${event?.lineno}:${event?.colno}\nstack: ${err.stack || 'no stack'}`;
|
||
this.reportError(msg);
|
||
}, true);
|
||
|
||
// 2) Promise 未捕获
|
||
window.addEventListener('unhandledrejection', (event: any) => {
|
||
const r: any = event?.reason || {};
|
||
const msg = `unhandledrejection: ${r.message || String(r)}\nstack: ${r.stack || 'no stack'}`;
|
||
this.reportError(msg);
|
||
}, true);
|
||
|
||
// 3) 覆盖 console
|
||
const rawErr = console.error.bind(console);
|
||
const rawWarn = console.warn.bind(console);
|
||
console.error = (...args: any[]) => {
|
||
try { this.reportError(args.map(a => (a && a.stack) ? a.stack : String(a)).join(' ')); } catch { }
|
||
rawErr(...args);
|
||
};
|
||
console.warn = (...args: any[]) => {
|
||
try { this.reportError(args.map(a => (a && a.stack) ? a.stack : String(a)).join(' ')); } catch { }
|
||
rawWarn(...args);
|
||
};
|
||
|
||
// 4) 覆盖 Cocos 日志(引擎内部 try/catch 只会调 cc.error/cc.warn)
|
||
const ccAny: any = (window as any).cc;
|
||
if (ccAny && !ccAny.__rp_hooked__) {
|
||
ccAny.__rp_hooked__ = true;
|
||
const e0 = ccAny.error?.bind(ccAny);
|
||
const e1 = ccAny.errorID?.bind(ccAny);
|
||
const w0 = ccAny.warn?.bind(ccAny);
|
||
if (e0) ccAny.error = (...args: any[]) => { try { this.reportError(args.map(String).join(' ')); } catch { } e0(...args); };
|
||
if (e1) ccAny.errorID = (...args: any[]) => { try { this.reportError(args.map(String).join(' ')); } catch { } e1(...args); };
|
||
if (w0) ccAny.warn = (...args: any[]) => { try { this.reportError(args.map(String).join(' ')); } catch { } w0(...args); };
|
||
}
|
||
}
|
||
|
||
// 简单的指纹生成方法
|
||
private generateErrorFingerprint(error: string): string {
|
||
// 只取错误消息的前100个字符作为指纹
|
||
return error.substring(0, 100);
|
||
}
|
||
|
||
// 手动上报错误
|
||
public reportError(errorMessage: string): void {
|
||
try {
|
||
let fingerprint = this.generateErrorFingerprint(errorMessage);
|
||
log("start report error", fingerprint);
|
||
// 检查是否已经上报过相同错误
|
||
if (this.reportedErrors.has(fingerprint)) {
|
||
log("same error already reported, skip");
|
||
return;
|
||
}
|
||
// 记录已上报错误
|
||
this.reportedErrors.add(fingerprint);
|
||
|
||
uploadError(errorMessage)
|
||
} catch (err) {
|
||
log("report error error", err);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
// 初始化全局错误管理器
|
||
export function initErrorManager() {
|
||
let errorManager = ErrorManager.instance;
|
||
return errorManager;
|
||
} |