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.
139 lines
4.7 KiB
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];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|