You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
AdLike2048/assets/mx/tools/PfDiagnostics.ts

139 lines
4.7 KiB

3 weeks ago
import { DEV } from 'cc/env';
import { membershipFromArray, type ReadonlyMembershipSet } from './MembershipSet';
/** 启动早期诊断仅需的 gg 子集;禁止 import GG(会拉起 GameController,与 GameLaunch 循环依赖) */
interface PfDiagGg {
sdk?: {
pf?: { getRealtimeLogManager?: (opts?: { syncToConsole?: boolean }) => PfRealtimeLogger };
pfLog?: (msg: string) => void;
};
game?: {
CurentBattle?: {
getReportDYChapterId?: () => number;
CurentWaveNum?: number;
};
};
data?: { doc?: { chapter?: number; rankId?: number } };
}
interface PfRealtimeLogger {
error?: (m: string) => void;
warn?: (m: string) => void;
info?: (m: string) => void;
log?: (m: string) => void;
addFilterMsg?: (m: string) => void;
}
/**
* 线上诊断:仅白名单 tag 上传平台(pfLog + RealtimeLogManager)。
* 其余只打 DEV console,避免误报占满后台配额。
* 单条 ≤256 字符;同 key 默认 10 分钟冷却。
*/
export class PfDiagnostics {
private static _realtime: { error?: (m: string) => void; warn?: (m: string) => void; info?: (m: string) => void; log?: (m: string) => void } | null = null;
private static _filterReady = false;
private static _lastMsByKey: Record<string, number> = {};
/** 小游戏分包可能早于 GG.ts 执行,禁止裸用全局 gg */
private static _gg(): PfDiagGg | undefined {
return (typeof globalThis !== 'undefined' ? (globalThis as { gg?: PfDiagGg }).gg : undefined);
}
/** 仅这些 key 实际上传抖音/微信日志后台(预警波次类 WARN 仅本地,不上传) */
private static readonly PLATFORM_UPLOAD_KEYS: ReadonlyMembershipSet<string> = membershipFromArray([
'BATTLE:STUCK_PAUSE',
'SETTLE:OPEN_FAIL',
'SKILL:OPEN_FAIL',
'STACK:BLOCK_STACK',
'STACK:PIN_COLLAPSE',
'MEM:WARN',
'CACHE:PATCH_OK',
'CACHE:LRU_CLEAR',
'CACHE:UNZIP_RETRY',
'CACHE:UNZIP_FAIL',
]);
private static _realtimeLogger() {
if (this._realtime !== null) return this._realtime;
const gg = this._gg();
const pf = gg?.sdk?.pf ?? (typeof globalThis !== 'undefined' ? (globalThis as any).tt : null);
if (!pf?.getRealtimeLogManager) {
this._realtime = null;
return null;
}
try {
const logger = pf.getRealtimeLogManager({ syncToConsole: !!DEV });
if (!this._filterReady) {
logger.addFilterMsg?.('xxfkz');
this._filterReady = true;
}
this._realtime = logger;
} catch {
this._realtime = null;
}
return this._realtime;
}
private static _battlePrefix(): string {
const gg = this._gg();
if (!gg) return 'ch0_w0';
const b = gg.game?.CurentBattle;
const ch = b?.getReportDYChapterId?.() ?? gg.data?.doc?.rankId ?? gg.data?.doc?.chapter ?? 0;
const wave = b?.CurentWaveNum ?? 0;
return `ch${ch}_w${wave}`;
}
static shouldUploadPlatform(domain: string, tag: string): boolean {
return this.PLATFORM_UPLOAD_KEYS.has(`${domain}:${tag}`);
}
/** @returns 是否实际上传平台(冷却或未在白名单会返回 false) */
static write(
domain: string,
tag: string,
detail: string,
cooldownMs = 600000,
level: 'error' | 'warn' | 'info' = 'warn',
): boolean {
const key = `${domain}:${tag}`;
const upload = this.shouldUploadPlatform(domain, tag);
let msg = `${this._battlePrefix()}-${domain}-${tag}-${detail}`;
if (msg.length > 250) msg = msg.slice(0, 250);
if (DEV) {
console.log('[PfDiag]', upload ? msg : `[local-only] ${msg}`);
}
if (!upload) return false;
const now = Date.now();
const last = this._lastMsByKey[key] ?? 0;
if (now - last < cooldownMs) return false;
this._lastMsByKey[key] = now;
try {
this._gg()?.sdk?.pfLog?.(msg);
} catch { /* ignore */ }
const logger = this._realtimeLogger();
if (logger) {
const fn = logger[level] ?? logger.warn ?? logger.log;
try {
fn?.call(logger, msg);
} catch { /* ignore */ }
}
return true;
}
/** 离开战斗回大厅:清掉战斗域诊断冷却,新局可重新上报 */
static resetBattleSession(): void {
const battleDomains = membershipFromArray(['BATTLE', 'STACK', 'SETTLE', 'SKILL']);
for (const key of Object.keys(this._lastMsByKey)) {
if (battleDomains.has(key.split(':')[0] ?? '')) {
delete this._lastMsByKey[key];
}
}
}
}