@zhushanwen/pi-extension-logger 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/__tests__/extension-logger.test.ts +27 -27
- package/src/index.ts +16 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-extension-logger",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Shared logging helper for Pi extensions — two-channel routing (audit appendEntry / debug file log; the third AI-realtime channel stays with pi native) (shared library, not a Pi extension - no install.mjs needed)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -27,7 +27,7 @@ describe("extension-logger", () => {
|
|
|
27
27
|
setPiHandle(undefined);
|
|
28
28
|
clearRateLimiterState();
|
|
29
29
|
vi.restoreAllMocks();
|
|
30
|
-
// 还原环境变量,避免文件日志测试的
|
|
30
|
+
// 还原环境变量,避免文件日志测试的 TAIJI_AGENT_DEBUG/PI_CODING_AGENT_DIR 泄漏到其它用例
|
|
31
31
|
process.env = { ...prevEnv };
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -140,7 +140,7 @@ describe("extension-logger", () => {
|
|
|
140
140
|
});
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
describe("
|
|
143
|
+
describe("TAIJI_AGENT_DEBUG 文件日志", () => {
|
|
144
144
|
// 文件日志的 agentDir 通过 getAgentDir() 推导(读 PI_CODING_AGENT_DIR,
|
|
145
145
|
// 默认 ~/.pi/agent)。设 PI_CODING_AGENT_DIR 到 tmpdir 子目录,与组 A 的
|
|
146
146
|
// arch-boundary 改动(fileLog 用 getAgentDir())保持一致。
|
|
@@ -159,15 +159,15 @@ describe("extension-logger", () => {
|
|
|
159
159
|
}
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
-
it("
|
|
163
|
-
delete process.env.
|
|
162
|
+
it("TAIJI_AGENT_DEBUG 未设时 debug 是 no-op(不抛错即可)", () => {
|
|
163
|
+
delete process.env.TAIJI_AGENT_DEBUG;
|
|
164
164
|
const logger = createLogger("test", pi);
|
|
165
165
|
expect(() => logger.debug("no-op")).not.toThrow();
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
// ---- Suggestion #8:fileLog 实际写入路径 ----
|
|
169
|
-
it("
|
|
170
|
-
process.env.
|
|
169
|
+
it("TAIJI_AGENT_DEBUG=1 时 debug 写入日志文件,内容含 [debug] 与序列化 data", () => {
|
|
170
|
+
process.env.TAIJI_AGENT_DEBUG = "1";
|
|
171
171
|
// 固定日期 → 文件名 <extName>-2026-08-01.log
|
|
172
172
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
|
173
173
|
|
|
@@ -190,8 +190,8 @@ describe("extension-logger", () => {
|
|
|
190
190
|
expect(appendSpy).not.toHaveBeenCalled();
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
it("
|
|
194
|
-
process.env.
|
|
193
|
+
it("TAIJI_AGENT_DEBUG=1 时 warn 既写文件又走 appendEntry(文件内容含 [warn])", () => {
|
|
194
|
+
process.env.TAIJI_AGENT_DEBUG = "1";
|
|
195
195
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
|
196
196
|
|
|
197
197
|
const logger = createLogger("warn-ext", pi);
|
|
@@ -207,8 +207,8 @@ describe("extension-logger", () => {
|
|
|
207
207
|
expect(appendSpy).toHaveBeenCalledOnce();
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
it("
|
|
211
|
-
process.env.
|
|
210
|
+
it("TAIJI_AGENT_DEBUG=1 写失败(只读目录)不 throw", () => {
|
|
211
|
+
process.env.TAIJI_AGENT_DEBUG = "1";
|
|
212
212
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
|
213
213
|
|
|
214
214
|
// 已存在的 logs 目录改为只读,让 mkdirSync/appendFileSync 失败。
|
|
@@ -243,13 +243,13 @@ describe("extension-logger", () => {
|
|
|
243
243
|
const [, entry] = appendSpy.mock.calls[0]!;
|
|
244
244
|
// appendEntry payload 的 data 字段保留原对象(safeStringify 仅在文件日志路径用)
|
|
245
245
|
expect(entry).toMatchObject({ level: "warn" });
|
|
246
|
-
// 验证 safeStringify 的 fallback 分支:用
|
|
246
|
+
// 验证 safeStringify 的 fallback 分支:用 TAIJI_AGENT_DEBUG=1 触发 fileLog,
|
|
247
247
|
// 文件内 data 应是 String() 形式(含 [object Object] 或循环结构字符串)
|
|
248
248
|
expect(() => JSON.stringify(cyclic)).toThrow(); // 对照:原对象确实不可序列化
|
|
249
249
|
});
|
|
250
250
|
|
|
251
251
|
it("BigInt:warn 不崩,文件日志路径走 String fallback", () => {
|
|
252
|
-
process.env.
|
|
252
|
+
process.env.TAIJI_AGENT_DEBUG = "1";
|
|
253
253
|
const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-ext-bigint-"));
|
|
254
254
|
process.env.PI_CODING_AGENT_DIR = tmpAgentDir;
|
|
255
255
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
|
@@ -274,16 +274,16 @@ describe("extension-logger", () => {
|
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
// ============================================================
|
|
277
|
-
//
|
|
277
|
+
// TAIJI_AGENT_EXT_LOG 托管观测档(设计 file-lock-unification-and-reaper-sink §3.2-D4)
|
|
278
278
|
// ============================================================
|
|
279
|
-
describe("
|
|
279
|
+
describe("TAIJI_AGENT_EXT_LOG 托管观测档", () => {
|
|
280
280
|
let tmpAgentDir: string;
|
|
281
281
|
|
|
282
282
|
beforeEach(() => {
|
|
283
283
|
tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-ext-extlog-"));
|
|
284
284
|
process.env.PI_CODING_AGENT_DIR = tmpAgentDir;
|
|
285
|
-
delete process.env.
|
|
286
|
-
delete process.env.
|
|
285
|
+
delete process.env.TAIJI_AGENT_DEBUG;
|
|
286
|
+
delete process.env.TAIJI_AGENT_EXT_LOG;
|
|
287
287
|
resetExtLogCleanupForTest();
|
|
288
288
|
vi.useFakeTimers();
|
|
289
289
|
});
|
|
@@ -302,8 +302,8 @@ describe("extension-logger", () => {
|
|
|
302
302
|
expect(existsSync(join(tmpAgentDir, "logs"))).toBe(false);
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
-
it("仅
|
|
306
|
-
process.env.
|
|
305
|
+
it("仅 TAIJI_AGENT_EXT_LOG=1:debug 调用以 info 级落盘(含序列化 data),不标 debug", () => {
|
|
306
|
+
process.env.TAIJI_AGENT_EXT_LOG = "1";
|
|
307
307
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
|
308
308
|
|
|
309
309
|
const logger = createLogger("extlog-ext", pi);
|
|
@@ -320,8 +320,8 @@ describe("extension-logger", () => {
|
|
|
320
320
|
expect(appendSpy).not.toHaveBeenCalled();
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
-
it("仅
|
|
324
|
-
process.env.
|
|
323
|
+
it("仅 TAIJI_AGENT_EXT_LOG=1:warn/error 照常落盘且保持原级标注", () => {
|
|
324
|
+
process.env.TAIJI_AGENT_EXT_LOG = "1";
|
|
325
325
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
|
326
326
|
|
|
327
327
|
const logger = createLogger("extlog-err", pi);
|
|
@@ -335,9 +335,9 @@ describe("extension-logger", () => {
|
|
|
335
335
|
expect(content).not.toContain("[info]");
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
-
it("
|
|
339
|
-
process.env.
|
|
340
|
-
process.env.
|
|
338
|
+
it("TAIJI_AGENT_DEBUG=1 与 EXT_LOG 同注入:按更详细的生效(debug 全量,原级标注)", () => {
|
|
339
|
+
process.env.TAIJI_AGENT_EXT_LOG = "1";
|
|
340
|
+
process.env.TAIJI_AGENT_DEBUG = "1";
|
|
341
341
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
|
342
342
|
|
|
343
343
|
const logger = createLogger("both-ext", pi);
|
|
@@ -355,7 +355,7 @@ describe("extension-logger", () => {
|
|
|
355
355
|
});
|
|
356
356
|
|
|
357
357
|
it("保留期清理:7 天前的 <ext>-<date>.log 被删,近期与本包 pattern 外文件保留", () => {
|
|
358
|
-
process.env.
|
|
358
|
+
process.env.TAIJI_AGENT_EXT_LOG = "1";
|
|
359
359
|
// fake timers 冻结 Date 在真实当前时刻:文件 mtime 走真实时钟,utimesSync 把
|
|
360
360
|
// old 的 mtime 设为 8 天前(> 7 天保留期),recent 与 unrelated 保留。
|
|
361
361
|
const logDir = join(tmpAgentDir, "logs");
|
|
@@ -381,7 +381,7 @@ describe("extension-logger", () => {
|
|
|
381
381
|
});
|
|
382
382
|
|
|
383
383
|
it("清理只认 <ext>-YYYY-MM-DD.log pattern,不动其他 .log 文件", () => {
|
|
384
|
-
process.env.
|
|
384
|
+
process.env.TAIJI_AGENT_EXT_LOG = "1";
|
|
385
385
|
const logDir = join(tmpAgentDir, "logs");
|
|
386
386
|
mkdirSync(logDir, { recursive: true });
|
|
387
387
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
@@ -510,8 +510,8 @@ describe("extension-logger", () => {
|
|
|
510
510
|
expect(appendSpy).toHaveBeenCalledTimes(10);
|
|
511
511
|
});
|
|
512
512
|
|
|
513
|
-
it("fileLog 通道不受限(
|
|
514
|
-
process.env.
|
|
513
|
+
it("fileLog 通道不受限(TAIJI_AGENT_DEBUG=1 时 100 条全写文件)", () => {
|
|
514
|
+
process.env.TAIJI_AGENT_DEBUG = "1";
|
|
515
515
|
const tmpAgentDir = mkdtempSync(join(tmpdir(), "pi-ext-ratelimit-"));
|
|
516
516
|
process.env.PI_CODING_AGENT_DIR = tmpAgentDir;
|
|
517
517
|
vi.setSystemTime(new Date("2026-08-01T12:34:56.789Z"));
|
package/src/index.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// 1. AI 实时 → tool result / block reason(pi 原生,本模块不涉及)
|
|
12
12
|
// 2. 事后排查 → pi.appendEntry(custom entry 不进 LLM 上下文,不显 TUI)【实装】
|
|
13
13
|
// 3. 开发者调试 → 文件日志,双开关分级(写 <agentDir>/logs/,均未注入默认 no-op)【实装】:
|
|
14
|
-
// -
|
|
15
|
-
// -
|
|
14
|
+
// - TAIJI_AGENT_DEBUG=1 → DEBUG 全量(现状语义,level 原样标注)
|
|
15
|
+
// - TAIJI_AGENT_EXT_LOG=1 → INFO 级落盘(taiji 托管环境由 runtime spawn 时经
|
|
16
16
|
// buildOutboundChildEnv extras 注入,设计 file-lock-unification-and-reaper-sink
|
|
17
17
|
// §3.2-D4 / U3-3)+ 7 天保留期清理;debug() 调用此模式下重标 info 写入
|
|
18
18
|
// - 两变量同时注入按更详细的生效(DEBUG 全量优先)
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
// 直接调 ctx.ui.notify。
|
|
23
23
|
//
|
|
24
24
|
// fileLog 单日文件无 size cap——显式豁免,依据:写入方为每 session 短命 pi 进程
|
|
25
|
-
// + 7 天保留期清理 + DEBUG
|
|
26
|
-
// docs/design/ext-simplify-14-shared-libs.md §7。
|
|
25
|
+
// + 7 天保留期清理 + DEBUG 档系开发者主动排障。
|
|
27
26
|
|
|
28
27
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
29
28
|
import {
|
|
@@ -48,11 +47,11 @@ import { join } from "node:path";
|
|
|
48
47
|
// - key = msg 原文(不包含 data 参数)。若调用方把动态 id 拼进 msg
|
|
49
48
|
// (如 `session=${id}`),每条 msg 不同则限流不命中。根治靠调用方把
|
|
50
49
|
// 动态值放 data 参数(D4 已声明)。
|
|
51
|
-
// - fileLog 通道全量不限流(
|
|
50
|
+
// - fileLog 通道全量不限流(TAIJI_AGENT_DEBUG=1 / TAIJI_AGENT_EXT_LOG=1 时日志写文件)。
|
|
52
51
|
// - Map cap 512 超限时全量清空(简化策略,对齐 cap-1024 先例),
|
|
53
52
|
// 等价于所有 key 窗口重置,防无界增长。
|
|
54
53
|
//
|
|
55
|
-
// 设计依据:
|
|
54
|
+
// 设计依据:extension-log-cleanup-design §3.4 D4
|
|
56
55
|
// ============================================================
|
|
57
56
|
|
|
58
57
|
/** 同 key 每窗口允许直写 appendEntry 的最大条数。 */
|
|
@@ -77,7 +76,7 @@ interface RateLimiterEntry {
|
|
|
77
76
|
* key = `${extName}:${level}:${msg}`(msg 为原 msg,非 prefixed)。
|
|
78
77
|
* 同进程所有 logger 实例共享(模块级 singleton)。
|
|
79
78
|
*
|
|
80
|
-
* ⚠️ 生命周期与进程一致——
|
|
79
|
+
* ⚠️ 生命周期与进程一致——taiji 每 session 一个独立 pi 进程
|
|
81
80
|
* (见 runtime process-manager.ts 的类注释),故不存在跨 session 残留问题。
|
|
82
81
|
*/
|
|
83
82
|
const rateLimiterState = new Map<string, RateLimiterEntry>();
|
|
@@ -157,7 +156,7 @@ export interface PiLike {
|
|
|
157
156
|
appendEntry?(customType: string, data?: unknown): void;
|
|
158
157
|
}
|
|
159
158
|
|
|
160
|
-
/** 日志级别。debug = 开发调试;info =
|
|
159
|
+
/** 日志级别。debug = 开发调试;info = TAIJI_AGENT_EXT_LOG 模式下 debug() 的落盘标注;warn/error = 内部降级与失败(事后排查价值)。 */
|
|
161
160
|
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
162
161
|
|
|
163
162
|
/**
|
|
@@ -166,8 +165,8 @@ type LogLevel = "debug" | "info" | "warn" | "error";
|
|
|
166
165
|
*/
|
|
167
166
|
export interface ExtensionLogger {
|
|
168
167
|
/**
|
|
169
|
-
* 开发调试日志。默认 no-op;
|
|
170
|
-
*
|
|
168
|
+
* 开发调试日志。默认 no-op;TAIJI_AGENT_DEBUG=1 时写文件日志(原级标注);
|
|
169
|
+
* TAIJI_AGENT_EXT_LOG=1(无 DEBUG)时以 info 级落盘(托管环境 INFO 观测档)。
|
|
171
170
|
* 不进 appendEntry。
|
|
172
171
|
*/
|
|
173
172
|
debug(msg: string, data?: unknown): void;
|
|
@@ -273,7 +272,7 @@ export function createLogger(extName: string, pi?: PiLike): ExtensionLogger {
|
|
|
273
272
|
// appendEntry 失败(session 已 disposed 等)→ 降级文件日志(下方 fileLog 兜底),不 throw
|
|
274
273
|
void appendErr;
|
|
275
274
|
}
|
|
276
|
-
// fileLog 全量不限流(
|
|
275
|
+
// fileLog 全量不限流(TAIJI_AGENT_DEBUG=1 排障时可见全部)
|
|
277
276
|
fileLog(extName, "warn", prefixed, data);
|
|
278
277
|
},
|
|
279
278
|
error(msg: string, data?: unknown): void {
|
|
@@ -348,10 +347,10 @@ function prefixMsg(extName: string, msg: string): string {
|
|
|
348
347
|
}
|
|
349
348
|
|
|
350
349
|
// ============================================================
|
|
351
|
-
//
|
|
350
|
+
// TAIJI_AGENT_EXT_LOG 托管观测档 + 保留期清理
|
|
352
351
|
// ============================================================
|
|
353
352
|
|
|
354
|
-
/** 托管观测档(
|
|
353
|
+
/** 托管观测档(TAIJI_AGENT_EXT_LOG=1)的日志保留天数,超期文件在首次落盘时清理。 */
|
|
355
354
|
const EXT_LOG_KEEP_DAYS = 7;
|
|
356
355
|
// 一天的毫秒数(数字分隔符形式对齐 session-reader hash-provider 的时间常量惯例)
|
|
357
356
|
const MS_PER_DAY = 86_400_000;
|
|
@@ -412,8 +411,8 @@ export function resetExtLogCleanupForTest(): void {
|
|
|
412
411
|
* 写文件日志到 `<agentDir>/logs/<extName>-YYYY-MM-DD.log`。
|
|
413
412
|
*
|
|
414
413
|
* 双开关分级(设计 file-lock-unification-and-reaper-sink §3.2-D4):
|
|
415
|
-
* -
|
|
416
|
-
* - 仅
|
|
414
|
+
* - TAIJI_AGENT_DEBUG=1 → DEBUG 全量,level 原样标注(现状语义不变);
|
|
415
|
+
* - 仅 TAIJI_AGENT_EXT_LOG=1 → INFO 级落盘:debug() 调用重标 info 写入,warn/error 照写;
|
|
417
416
|
* - 均未注入 → no-op(零 fs 调用,裸 pi 独立用户零磁盘/行为影响)。
|
|
418
417
|
* agentDir 通过 pi 的 SSOT `getAgentDir()` 推导(读
|
|
419
418
|
* `PI_CODING_AGENT_DIR`/`${APP_NAME}_CODING_AGENT_DIR`,默认 `~/.pi/agent`),
|
|
@@ -425,8 +424,8 @@ export function resetExtLogCleanupForTest(): void {
|
|
|
425
424
|
* 行可能交错(可接受——debug 日志不要求严格顺序)。
|
|
426
425
|
*/
|
|
427
426
|
function fileLog(extName: string, level: LogLevel, msg: string, data?: unknown): void {
|
|
428
|
-
const debugMode = process.env.
|
|
429
|
-
const extLogMode = !debugMode && process.env.
|
|
427
|
+
const debugMode = process.env.TAIJI_AGENT_DEBUG === "1";
|
|
428
|
+
const extLogMode = !debugMode && process.env.TAIJI_AGENT_EXT_LOG === "1";
|
|
430
429
|
if (!debugMode && !extLogMode) return;
|
|
431
430
|
// 托管观测档:debug() 调用降档标注为 info(INFO 级观测,非 DEBUG 全量)
|
|
432
431
|
const effectiveLevel: LogLevel = extLogMode && level === "debug" ? "info" : level;
|