112 lines
3.4 KiB
JavaScript
112 lines
3.4 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* 跨 Hook 会话追踪 (Phase 3)
|
|||
|
|
*
|
|||
|
|
* 提供共享 traceId,使 3 个 hooks 的事件可以关联分析。
|
|||
|
|
* 所有消费者必须 try/catch 包裹,fail-open。
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const { safeAppendJsonl } = require('../hooks/lib/safe-append.js');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
// ─── 路径解析 ────────────────────────────────────────
|
|||
|
|
let debugDir;
|
|||
|
|
try {
|
|||
|
|
const { PATHS } = require('./paths.config.js');
|
|||
|
|
debugDir = PATHS.debugDir;
|
|||
|
|
} catch {
|
|||
|
|
debugDir = path.resolve(__dirname, '..', 'debug');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const SESSION_TRACE_FILE = path.join(debugDir, 'session-trace.json');
|
|||
|
|
const SESSION_LOCK_FILE = path.join(debugDir, 'session-active.lock');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取当前会话的 traceId(同 session 复用)
|
|||
|
|
* @returns {{ sessionId: string, traceId: string, startedAt: string, eventCount: number }}
|
|||
|
|
*/
|
|||
|
|
function getSessionTrace() {
|
|||
|
|
if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
|
|||
|
|
|
|||
|
|
// 读取当前 sessionId
|
|||
|
|
let sessionId = 'unknown';
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(SESSION_LOCK_FILE)) {
|
|||
|
|
const lock = JSON.parse(fs.readFileSync(SESSION_LOCK_FILE, 'utf8'));
|
|||
|
|
sessionId = lock.sessionId || lock.id || 'unknown';
|
|||
|
|
}
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
// 读取或创建 trace 状态
|
|||
|
|
let trace = null;
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(SESSION_TRACE_FILE)) {
|
|||
|
|
trace = JSON.parse(fs.readFileSync(SESSION_TRACE_FILE, 'utf8'));
|
|||
|
|
}
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
// 同 session 复用 traceId,否则创建新的
|
|||
|
|
if (trace && trace.sessionId === sessionId && sessionId !== 'unknown') {
|
|||
|
|
return trace;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 新 trace
|
|||
|
|
const traceId = `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|||
|
|
const newTrace = {
|
|||
|
|
sessionId,
|
|||
|
|
traceId,
|
|||
|
|
startedAt: new Date().toISOString(),
|
|||
|
|
eventCount: 0,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// W5: 原子写入 (temp+rename)
|
|||
|
|
var tmp = SESSION_TRACE_FILE + '.tmp.' + process.pid;
|
|||
|
|
fs.writeFileSync(tmp, JSON.stringify(newTrace, null, 2) + '\n');
|
|||
|
|
fs.renameSync(tmp, SESSION_TRACE_FILE);
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
return newTrace;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 追加事件到日期分片的 trace 日志
|
|||
|
|
* @param {string} hookName - 来源 hook 名称
|
|||
|
|
* @param {string} eventType - 事件类型 (detection|block-warn|pass|outcome)
|
|||
|
|
* @param {Object} data - 附加数据
|
|||
|
|
*/
|
|||
|
|
function appendTraceEvent(hookName, eventType, data) {
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
|
|||
|
|
|
|||
|
|
const trace = getSessionTrace();
|
|||
|
|
const dateStr = new Date().toISOString().slice(0, 10);
|
|||
|
|
const logFile = path.join(debugDir, `trace-${dateStr}.jsonl`);
|
|||
|
|
|
|||
|
|
const event = {
|
|||
|
|
ts: new Date().toISOString(),
|
|||
|
|
traceId: trace.traceId,
|
|||
|
|
sessionId: trace.sessionId,
|
|||
|
|
hook: hookName,
|
|||
|
|
event: eventType,
|
|||
|
|
data: data || {},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
safeAppendJsonl(logFile, event);
|
|||
|
|
|
|||
|
|
// 更新 eventCount (原子写入)
|
|||
|
|
trace.eventCount = (trace.eventCount || 0) + 1;
|
|||
|
|
try {
|
|||
|
|
var tmp2 = SESSION_TRACE_FILE + '.tmp.' + process.pid;
|
|||
|
|
fs.writeFileSync(tmp2, JSON.stringify(trace, null, 2) + '\n');
|
|||
|
|
fs.renameSync(tmp2, SESSION_TRACE_FILE);
|
|||
|
|
} catch {}
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 导出 ─────────────────────────────────────────────
|
|||
|
|
if (typeof module !== 'undefined') {
|
|||
|
|
module.exports = { getSessionTrace, appendTraceEvent };
|
|||
|
|
}
|