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 };
|
||
}
|