/** * 会话取证 · schema 探测:解压多帧 zstd → 事件类型直方图 + 每类样本键结构(先摸清格式) * 会话 schema 探测(只读):输出事件类型直方图 + 少量样本键结构 * 用法:node probe-schema.mjs */ import { readFileSync } from 'node:fs' import { zstdDecompressSync } from 'node:zlib' const raw = readFileSync(process.argv[2]) const MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]) const offs = [] for (let i = 0; i + 4 <= raw.length; i++) if (raw.compare(MAGIC, 0, 4, i, i + 4) === 0) offs.push(i) let text = '' const frames = offs.length ? offs : [0] for (let f = 0; f < frames.length; f++) { const s = frames[f], e = f + 1 < frames.length ? frames[f + 1] : raw.length try { text += zstdDecompressSync(raw.subarray(s, e)).toString('utf8') } catch {} } const lines = text.split('\n').filter((l) => l.trim()) console.log('解压后 %d 行 / %d 字符 / zstd 帧 %d', lines.length, text.length, frames.length) const types = new Map() const samples = new Map() for (const l of lines) { let o try { o = JSON.parse(l) } catch { continue } const t = o.type ?? o.event ?? o.kind ?? '(no-type)' types.set(t, (types.get(t) ?? 0) + 1) if (!samples.has(t)) samples.set(t, o) } console.log('\n=== 事件类型直方图 ===') for (const [t, n] of [...types].sort((a, b) => b[1] - a[1])) console.log(' %-28s %d', t, n) console.log('\n=== 每类样本(键 + 截断值) ===') for (const [t, o] of samples) { console.log('--- %s ---', t) const dump = (v, depth = 0) => { if (v === null) return 'null' if (Array.isArray(v)) return `[${v.length} items${v.length ? ': ' + dump(v[0], depth + 1) : ''}]` if (typeof v === 'object') { if (depth > 1) return '{…}' return '{' + Object.entries(v).slice(0, 10).map(([k, x]) => `${k}: ${dump(x, depth + 1)}`).join(', ') + '}' } const s = String(v) return JSON.stringify(s.length > 90 ? s.slice(0, 90) + '…' : s) } console.log(' ' + dump(o).slice(0, 1200)) }