pi-harness-runtime 1.1.394 → 1.1.396
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/AGENTS.md +135 -4
- package/harness/glm-quota-logger.ts +17 -0
- package/harness/langchain/planner-logger.ts +15 -0
- package/harness/service-diagnostics.ts +32 -1
- package/index.ts +92 -3
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/workspace-env-format.d.ts +87 -0
- package/packages/contracts/dist/workspace-env-format.js +174 -6
- package/packages/contracts/dist/workspace-env.js +120 -9
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/workspace-env-format.ts +209 -6
- package/packages/contracts/src/workspace-env.ts +128 -7
- package/packages/contracts/test/workspace-env.test.ts +177 -0
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/dist/continuation-controller.d.ts +47 -0
- package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
- package/packages/jev-judge/dist/continuation-controller.js +142 -16
- package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
- package/packages/jev-judge/dist/index.d.ts +6 -0
- package/packages/jev-judge/dist/index.d.ts.map +1 -1
- package/packages/jev-judge/dist/index.js +6 -0
- package/packages/jev-judge/dist/index.js.map +1 -1
- package/packages/jev-judge/dist/validation-ledger.d.ts +129 -0
- package/packages/jev-judge/dist/validation-ledger.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-ledger.js +234 -0
- package/packages/jev-judge/dist/validation-ledger.js.map +1 -0
- package/packages/jev-judge/dist/validation-plan.d.ts +176 -0
- package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-plan.js +293 -0
- package/packages/jev-judge/dist/validation-plan.js.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts +48 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js +83 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/continuation-controller.ts +179 -20
- package/packages/jev-judge/src/index.ts +41 -0
- package/packages/jev-judge/src/validation-ledger.ts +290 -0
- package/packages/jev-judge/src/validation-plan.ts +406 -0
- package/packages/jev-judge/src/validation-tie-breaker.ts +123 -0
- package/packages/jev-judge/tests/continuation-controller.test.ts +167 -0
- package/packages/jev-judge/tests/validation-ledger.test.ts +181 -0
- package/packages/jev-judge/tests/validation-plan.test.ts +170 -0
- package/packages/jev-judge/tests/validation-tie-breaker.test.ts +88 -0
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/README.md +37 -0
- package/packages/logging/dist/buffer.d.ts +33 -0
- package/packages/logging/dist/buffer.d.ts.map +1 -0
- package/packages/logging/dist/buffer.js +60 -0
- package/packages/logging/dist/buffer.js.map +1 -0
- package/packages/logging/dist/config.d.ts +40 -0
- package/packages/logging/dist/config.d.ts.map +1 -0
- package/packages/logging/dist/config.js +114 -0
- package/packages/logging/dist/config.js.map +1 -0
- package/packages/logging/dist/index.d.ts +32 -0
- package/packages/logging/dist/index.d.ts.map +1 -0
- package/packages/logging/dist/index.js +31 -0
- package/packages/logging/dist/index.js.map +1 -0
- package/packages/logging/dist/redact.d.ts +43 -0
- package/packages/logging/dist/redact.d.ts.map +1 -0
- package/packages/logging/dist/redact.js +220 -0
- package/packages/logging/dist/redact.js.map +1 -0
- package/packages/logging/dist/sampler.d.ts +37 -0
- package/packages/logging/dist/sampler.d.ts.map +1 -0
- package/packages/logging/dist/sampler.js +78 -0
- package/packages/logging/dist/sampler.js.map +1 -0
- package/packages/logging/dist/shipper.d.ts +82 -0
- package/packages/logging/dist/shipper.d.ts.map +1 -0
- package/packages/logging/dist/shipper.js +238 -0
- package/packages/logging/dist/shipper.js.map +1 -0
- package/packages/logging/dist/singleton.d.ts +38 -0
- package/packages/logging/dist/singleton.d.ts.map +1 -0
- package/packages/logging/dist/singleton.js +95 -0
- package/packages/logging/dist/singleton.js.map +1 -0
- package/packages/logging/dist/spool.d.ts +39 -0
- package/packages/logging/dist/spool.d.ts.map +1 -0
- package/packages/logging/dist/spool.js +110 -0
- package/packages/logging/dist/spool.js.map +1 -0
- package/packages/logging/dist/transports/r2.d.ts +31 -0
- package/packages/logging/dist/transports/r2.d.ts.map +1 -0
- package/packages/logging/dist/transports/r2.js +60 -0
- package/packages/logging/dist/transports/r2.js.map +1 -0
- package/packages/logging/dist/transports/webhook.d.ts +22 -0
- package/packages/logging/dist/transports/webhook.d.ts.map +1 -0
- package/packages/logging/dist/transports/webhook.js +37 -0
- package/packages/logging/dist/transports/webhook.js.map +1 -0
- package/packages/logging/dist/types.d.ts +73 -0
- package/packages/logging/dist/types.d.ts.map +1 -0
- package/packages/logging/dist/types.js +19 -0
- package/packages/logging/dist/types.js.map +1 -0
- package/packages/logging/package.json +37 -0
- package/packages/logging/src/buffer.ts +77 -0
- package/packages/logging/src/config.ts +163 -0
- package/packages/logging/src/index.ts +87 -0
- package/packages/logging/src/redact.ts +271 -0
- package/packages/logging/src/sampler.ts +97 -0
- package/packages/logging/src/shipper.ts +281 -0
- package/packages/logging/src/singleton.ts +142 -0
- package/packages/logging/src/spool.ts +139 -0
- package/packages/logging/src/transports/r2.ts +86 -0
- package/packages/logging/src/transports/webhook.ts +54 -0
- package/packages/logging/src/types.ts +85 -0
- package/packages/logging/test/buffer.test.ts +44 -0
- package/packages/logging/test/config.test.ts +97 -0
- package/packages/logging/test/integration.test.ts +135 -0
- package/packages/logging/test/redact.test.ts +109 -0
- package/packages/logging/test/sampler.test.ts +57 -0
- package/packages/logging/test/shipper.test.ts +152 -0
- package/packages/logging/test/spool.test.ts +93 -0
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/unified-webhook-server.ts +102 -0
- package/scripts/validation-run.ts +191 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shipper owns the delivery decision.
|
|
3
|
+
*
|
|
4
|
+
* Producers call `log()`. The shipper redacts, samples, buffers, and then
|
|
5
|
+
* delivers a batch through the configured transports in priority order:
|
|
6
|
+
*
|
|
7
|
+
* R2 (durable archive) -> webhook (live) -> local spool (last resort)
|
|
8
|
+
*
|
|
9
|
+
* If every transport fails the batch is spooled to disk and retried on the
|
|
10
|
+
* next flush, so a temporary outage costs latency, not data.
|
|
11
|
+
*/
|
|
12
|
+
import { type LogBufferOptions } from "./buffer.js";
|
|
13
|
+
import { type RedactionOptions } from "./redact.js";
|
|
14
|
+
import { type SamplingPolicy } from "./sampler.js";
|
|
15
|
+
import { LogSpool } from "./spool.js";
|
|
16
|
+
import type { FlushStats, LogInput, LogLevel, LogTransport } from "./types.js";
|
|
17
|
+
export interface LogShipperOptions {
|
|
18
|
+
/** Application name reported with every batch. */
|
|
19
|
+
app?: string;
|
|
20
|
+
/** Host name reported with every batch. */
|
|
21
|
+
host?: string;
|
|
22
|
+
/** Transports in priority order. */
|
|
23
|
+
transports?: LogTransport[];
|
|
24
|
+
/** Local spool used when every transport fails. */
|
|
25
|
+
spool?: LogSpool;
|
|
26
|
+
/** Buffer bounds. */
|
|
27
|
+
buffer?: LogBufferOptions;
|
|
28
|
+
/** Sampling policy. */
|
|
29
|
+
sampling?: Partial<SamplingPolicy>;
|
|
30
|
+
/** Redaction limits. */
|
|
31
|
+
redaction?: RedactionOptions;
|
|
32
|
+
/** Interval for the automatic flush timer. Default 15000 ms. */
|
|
33
|
+
flushIntervalMs?: number;
|
|
34
|
+
/** Clock injection (tests). */
|
|
35
|
+
now?: () => number;
|
|
36
|
+
/** Batch id factory (tests). */
|
|
37
|
+
createBatchId?: () => string;
|
|
38
|
+
}
|
|
39
|
+
export declare class LogShipper {
|
|
40
|
+
private readonly app;
|
|
41
|
+
private readonly host;
|
|
42
|
+
private readonly transports;
|
|
43
|
+
private readonly spool;
|
|
44
|
+
private readonly buffer;
|
|
45
|
+
private readonly sampler;
|
|
46
|
+
private readonly redaction;
|
|
47
|
+
private readonly flushIntervalMs;
|
|
48
|
+
private readonly now;
|
|
49
|
+
private readonly createBatchId;
|
|
50
|
+
private timer;
|
|
51
|
+
private flushing;
|
|
52
|
+
private stats;
|
|
53
|
+
constructor(options?: LogShipperOptions);
|
|
54
|
+
/** Record a log event. Never throws. */
|
|
55
|
+
log(input: LogInput): void;
|
|
56
|
+
/** Convenience wrappers around `log()`. */
|
|
57
|
+
debug(source: string, event: string, message?: string, fields?: Record<string, unknown>): void;
|
|
58
|
+
info(source: string, event: string, message?: string, fields?: Record<string, unknown>): void;
|
|
59
|
+
warn(source: string, event: string, message?: string, fields?: Record<string, unknown>): void;
|
|
60
|
+
error(source: string, event: string, message?: string, fields?: Record<string, unknown>, error?: unknown): void;
|
|
61
|
+
/** Deliver buffered records (and drain the spool). Never throws. */
|
|
62
|
+
flush(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Synchronous last resort used on process exit: whatever is still in
|
|
65
|
+
* memory is written to the local spool so it is not lost.
|
|
66
|
+
*/
|
|
67
|
+
flushSync(): void;
|
|
68
|
+
/** Start the periodic flush timer. */
|
|
69
|
+
start(): void;
|
|
70
|
+
/** Stop the timer and flush once more. */
|
|
71
|
+
stop(): Promise<void>;
|
|
72
|
+
getStats(): FlushStats;
|
|
73
|
+
private buildRecord;
|
|
74
|
+
private buildBatch;
|
|
75
|
+
private doFlush;
|
|
76
|
+
/** Try every transport in order. Returns true when one accepted the batch. */
|
|
77
|
+
private deliver;
|
|
78
|
+
/** Re-send spooled files. Stops at the first failure. */
|
|
79
|
+
private drainSpool;
|
|
80
|
+
}
|
|
81
|
+
export type { LogLevel };
|
|
82
|
+
//# sourceMappingURL=shipper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipper.d.ts","sourceRoot":"","sources":["../src/shipper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shipper owns the delivery decision.
|
|
3
|
+
*
|
|
4
|
+
* Producers call `log()`. The shipper redacts, samples, buffers, and then
|
|
5
|
+
* delivers a batch through the configured transports in priority order:
|
|
6
|
+
*
|
|
7
|
+
* R2 (durable archive) -> webhook (live) -> local spool (last resort)
|
|
8
|
+
*
|
|
9
|
+
* If every transport fails the batch is spooled to disk and retried on the
|
|
10
|
+
* next flush, so a temporary outage costs latency, not data.
|
|
11
|
+
*/
|
|
12
|
+
import { hostname } from "node:os";
|
|
13
|
+
import { LogBuffer } from "./buffer.js";
|
|
14
|
+
import { redactFields, redactText } from "./redact.js";
|
|
15
|
+
import { LogSampler } from "./sampler.js";
|
|
16
|
+
function defaultBatchId() {
|
|
17
|
+
const stamp = new Date().toISOString().replace(/[-:.]/g, "").slice(0, 15);
|
|
18
|
+
return `${stamp}-${Math.random().toString(36).slice(2, 8)}`;
|
|
19
|
+
}
|
|
20
|
+
function toErrorInfo(error) {
|
|
21
|
+
if (error instanceof Error) {
|
|
22
|
+
const info = { name: error.name, message: error.message };
|
|
23
|
+
if (error.stack)
|
|
24
|
+
info.stack = error.stack;
|
|
25
|
+
return info;
|
|
26
|
+
}
|
|
27
|
+
return { name: "Error", message: String(error) };
|
|
28
|
+
}
|
|
29
|
+
export class LogShipper {
|
|
30
|
+
app;
|
|
31
|
+
host;
|
|
32
|
+
transports;
|
|
33
|
+
spool;
|
|
34
|
+
buffer;
|
|
35
|
+
sampler;
|
|
36
|
+
redaction;
|
|
37
|
+
flushIntervalMs;
|
|
38
|
+
now;
|
|
39
|
+
createBatchId;
|
|
40
|
+
timer;
|
|
41
|
+
flushing;
|
|
42
|
+
stats = {
|
|
43
|
+
shipped: 0,
|
|
44
|
+
spooled: 0,
|
|
45
|
+
dropped: 0,
|
|
46
|
+
sampled: 0,
|
|
47
|
+
errors: {},
|
|
48
|
+
};
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
this.app = options.app ?? "pi-harness";
|
|
51
|
+
this.host = options.host ?? hostname();
|
|
52
|
+
this.transports = options.transports ?? [];
|
|
53
|
+
this.spool = options.spool;
|
|
54
|
+
this.buffer = new LogBuffer(options.buffer);
|
|
55
|
+
this.sampler = new LogSampler(options.sampling);
|
|
56
|
+
this.redaction = options.redaction ?? {};
|
|
57
|
+
this.flushIntervalMs = options.flushIntervalMs ?? 15_000;
|
|
58
|
+
this.now = options.now ?? (() => Date.now());
|
|
59
|
+
this.createBatchId = options.createBatchId ?? defaultBatchId;
|
|
60
|
+
}
|
|
61
|
+
/** Record a log event. Never throws. */
|
|
62
|
+
log(input) {
|
|
63
|
+
try {
|
|
64
|
+
const record = this.buildRecord(input);
|
|
65
|
+
if (!this.sampler.shouldLog(record, this.now())) {
|
|
66
|
+
this.stats.sampled++;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.buffer.push(record);
|
|
70
|
+
if (this.buffer.size >= this.buffer.batchSize) {
|
|
71
|
+
void this.flush();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// A logging failure must never break the caller.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Convenience wrappers around `log()`. */
|
|
79
|
+
debug(source, event, message, fields) {
|
|
80
|
+
this.log({ level: "debug", source, event, message, fields });
|
|
81
|
+
}
|
|
82
|
+
info(source, event, message, fields) {
|
|
83
|
+
this.log({ level: "info", source, event, message, fields });
|
|
84
|
+
}
|
|
85
|
+
warn(source, event, message, fields) {
|
|
86
|
+
this.log({ level: "warn", source, event, message, fields });
|
|
87
|
+
}
|
|
88
|
+
error(source, event, message, fields, error) {
|
|
89
|
+
this.log({ level: "error", source, event, message, fields, error });
|
|
90
|
+
}
|
|
91
|
+
/** Deliver buffered records (and drain the spool). Never throws. */
|
|
92
|
+
async flush() {
|
|
93
|
+
if (this.flushing)
|
|
94
|
+
return this.flushing;
|
|
95
|
+
this.flushing = this.doFlush().finally(() => {
|
|
96
|
+
this.flushing = undefined;
|
|
97
|
+
});
|
|
98
|
+
return this.flushing;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Synchronous last resort used on process exit: whatever is still in
|
|
102
|
+
* memory is written to the local spool so it is not lost.
|
|
103
|
+
*/
|
|
104
|
+
flushSync() {
|
|
105
|
+
try {
|
|
106
|
+
const records = this.buffer.peekAll();
|
|
107
|
+
if (records.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
this.buffer.clear();
|
|
110
|
+
if (!this.spool)
|
|
111
|
+
return;
|
|
112
|
+
this.spool.append(this.buildBatch(records));
|
|
113
|
+
this.stats.spooled += records.length;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Nothing else we can do at exit time.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Start the periodic flush timer. */
|
|
120
|
+
start() {
|
|
121
|
+
if (this.timer)
|
|
122
|
+
return;
|
|
123
|
+
this.timer = setInterval(() => {
|
|
124
|
+
void this.flush();
|
|
125
|
+
}, this.flushIntervalMs);
|
|
126
|
+
// Never keep the process alive just for log delivery.
|
|
127
|
+
this.timer.unref?.();
|
|
128
|
+
}
|
|
129
|
+
/** Stop the timer and flush once more. */
|
|
130
|
+
async stop() {
|
|
131
|
+
if (this.timer) {
|
|
132
|
+
clearInterval(this.timer);
|
|
133
|
+
this.timer = undefined;
|
|
134
|
+
}
|
|
135
|
+
await this.flush();
|
|
136
|
+
}
|
|
137
|
+
getStats() {
|
|
138
|
+
return {
|
|
139
|
+
...this.stats,
|
|
140
|
+
dropped: this.stats.dropped + this.buffer.dropped,
|
|
141
|
+
errors: { ...this.stats.errors },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
buildRecord(input) {
|
|
145
|
+
const record = {
|
|
146
|
+
ts: new Date(this.now()).toISOString(),
|
|
147
|
+
level: input.level,
|
|
148
|
+
source: input.source,
|
|
149
|
+
event: input.event,
|
|
150
|
+
};
|
|
151
|
+
if (input.message !== undefined) {
|
|
152
|
+
record.message = redactText(input.message, this.redaction.placeholder);
|
|
153
|
+
}
|
|
154
|
+
if (input.sessionId)
|
|
155
|
+
record.sessionId = input.sessionId;
|
|
156
|
+
if (typeof input.durationMs === "number")
|
|
157
|
+
record.durationMs = input.durationMs;
|
|
158
|
+
const fields = redactFields(input.fields, this.redaction);
|
|
159
|
+
if (fields && Object.keys(fields).length > 0)
|
|
160
|
+
record.fields = fields;
|
|
161
|
+
if (input.error !== undefined) {
|
|
162
|
+
const info = toErrorInfo(input.error);
|
|
163
|
+
record.error = {
|
|
164
|
+
name: info.name,
|
|
165
|
+
message: redactText(info.message, this.redaction.placeholder),
|
|
166
|
+
...(info.stack ? { stack: redactText(info.stack, this.redaction.placeholder) } : {}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return record;
|
|
170
|
+
}
|
|
171
|
+
buildBatch(records) {
|
|
172
|
+
return {
|
|
173
|
+
batchId: this.createBatchId(),
|
|
174
|
+
app: this.app,
|
|
175
|
+
host: this.host,
|
|
176
|
+
sentAt: new Date(this.now()).toISOString(),
|
|
177
|
+
records,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async doFlush() {
|
|
181
|
+
try {
|
|
182
|
+
await this.drainSpool();
|
|
183
|
+
const records = this.buffer.takeBatch();
|
|
184
|
+
if (records.length === 0)
|
|
185
|
+
return;
|
|
186
|
+
const batch = this.buildBatch(records);
|
|
187
|
+
const delivered = await this.deliver(batch);
|
|
188
|
+
if (delivered) {
|
|
189
|
+
this.stats.shipped += records.length;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (this.spool) {
|
|
193
|
+
this.spool.append(batch);
|
|
194
|
+
this.stats.spooled += records.length;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
this.stats.dropped += records.length;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Never let a flush failure escape.
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Try every transport in order. Returns true when one accepted the batch. */
|
|
205
|
+
async deliver(batch) {
|
|
206
|
+
for (const transport of this.transports) {
|
|
207
|
+
try {
|
|
208
|
+
await transport.send(batch);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
this.stats.errors[transport.name] =
|
|
213
|
+
error instanceof Error ? error.message : String(error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
/** Re-send spooled files. Stops at the first failure. */
|
|
219
|
+
async drainSpool() {
|
|
220
|
+
if (!this.spool)
|
|
221
|
+
return;
|
|
222
|
+
const files = this.spool.list();
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
const records = this.spool.read(file);
|
|
225
|
+
if (records.length === 0) {
|
|
226
|
+
this.spool.remove(file);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const batch = this.buildBatch(records);
|
|
230
|
+
const delivered = await this.deliver(batch);
|
|
231
|
+
if (!delivered)
|
|
232
|
+
return;
|
|
233
|
+
this.spool.remove(file);
|
|
234
|
+
this.stats.shipped += records.length;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=shipper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipper.js","sourceRoot":"","sources":["../src/shipper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The process-wide logging singleton.
|
|
3
|
+
*
|
|
4
|
+
* The singleton lives in the package (not in the harness) so that every
|
|
5
|
+
* producer - the root extension and the harness modules - shares exactly one
|
|
6
|
+
* shipper, one buffer and one spool.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here throws: a logging failure must never break the caller.
|
|
9
|
+
*/
|
|
10
|
+
import { type LogSinkConfig } from "./config.js";
|
|
11
|
+
import { LogShipper } from "./shipper.js";
|
|
12
|
+
import type { LogLevel } from "./types.js";
|
|
13
|
+
export interface InitCentralLoggingOptions {
|
|
14
|
+
/** Environment used for the configuration. Defaults to `process.env`. */
|
|
15
|
+
env?: Record<string, string | undefined>;
|
|
16
|
+
/** Directory holding the credential key files. */
|
|
17
|
+
keysDir?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the centralized logging pipeline.
|
|
21
|
+
*
|
|
22
|
+
* Calling it twice is safe: the second call returns the existing
|
|
23
|
+
* configuration without creating a second shipper.
|
|
24
|
+
*/
|
|
25
|
+
export declare function initCentralLogging(options?: InitCentralLoggingOptions): LogSinkConfig | null;
|
|
26
|
+
/** The active shipper, or null when logging is not initialized. */
|
|
27
|
+
export declare function getCentralShipper(): LogShipper | null;
|
|
28
|
+
/** The resolved configuration, or null when logging was never initialized. */
|
|
29
|
+
export declare function getCentralLogConfig(): LogSinkConfig | null;
|
|
30
|
+
/** Emit one record. No-op when the pipeline is not initialized. */
|
|
31
|
+
export declare function centralLog(level: LogLevel, source: string, event: string, message?: string, fields?: Record<string, unknown>, error?: unknown): void;
|
|
32
|
+
export declare function centralDebug(source: string, event: string, message?: string, fields?: Record<string, unknown>): void;
|
|
33
|
+
export declare function centralInfo(source: string, event: string, message?: string, fields?: Record<string, unknown>): void;
|
|
34
|
+
export declare function centralWarn(source: string, event: string, message?: string, fields?: Record<string, unknown>): void;
|
|
35
|
+
export declare function centralError(source: string, event: string, message?: string, fields?: Record<string, unknown>, error?: unknown): void;
|
|
36
|
+
/** Flush and stop the pipeline. Safe to call when it was never started. */
|
|
37
|
+
export declare function shutdownCentralLogging(): Promise<void>;
|
|
38
|
+
//# sourceMappingURL=singleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../src/singleton.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,yBAAyB;IACzC,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,GAAE,yBAA8B,GACrC,aAAa,GAAG,IAAI,CAiCtB;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,IAAI,UAAU,GAAG,IAAI,CAErD;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,IAAI,aAAa,GAAG,IAAI,CAE1D;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CACzB,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,KAAK,CAAC,EAAE,OAAO,GACb,IAAI,CAMN;AAED,wBAAgB,YAAY,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,IAAI,CAEN;AAED,wBAAgB,WAAW,CAC1B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,IAAI,CAEN;AAED,wBAAgB,WAAW,CAC1B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,IAAI,CAEN;AAED,wBAAgB,YAAY,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,KAAK,CAAC,EAAE,OAAO,GACb,IAAI,CAEN;AAED,2EAA2E;AAC3E,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS5D"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The process-wide logging singleton.
|
|
3
|
+
*
|
|
4
|
+
* The singleton lives in the package (not in the harness) so that every
|
|
5
|
+
* producer - the root extension and the harness modules - shares exactly one
|
|
6
|
+
* shipper, one buffer and one spool.
|
|
7
|
+
*
|
|
8
|
+
* Nothing here throws: a logging failure must never break the caller.
|
|
9
|
+
*/
|
|
10
|
+
import { resolveLogSinkConfig } from "./config.js";
|
|
11
|
+
import { LogShipper } from "./shipper.js";
|
|
12
|
+
import { LogSpool } from "./spool.js";
|
|
13
|
+
let shipper = null;
|
|
14
|
+
let config = null;
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the centralized logging pipeline.
|
|
17
|
+
*
|
|
18
|
+
* Calling it twice is safe: the second call returns the existing
|
|
19
|
+
* configuration without creating a second shipper.
|
|
20
|
+
*/
|
|
21
|
+
export function initCentralLogging(options = {}) {
|
|
22
|
+
if (config)
|
|
23
|
+
return config;
|
|
24
|
+
try {
|
|
25
|
+
const resolved = resolveLogSinkConfig({
|
|
26
|
+
env: options.env ?? process.env,
|
|
27
|
+
keysDir: options.keysDir,
|
|
28
|
+
});
|
|
29
|
+
config = resolved;
|
|
30
|
+
if (!resolved.enabled)
|
|
31
|
+
return resolved;
|
|
32
|
+
const spool = resolved.spoolDir ? new LogSpool({ dir: resolved.spoolDir }) : undefined;
|
|
33
|
+
shipper = new LogShipper({
|
|
34
|
+
app: resolved.app,
|
|
35
|
+
transports: resolved.transports,
|
|
36
|
+
spool,
|
|
37
|
+
buffer: resolved.buffer,
|
|
38
|
+
sampling: resolved.sampling,
|
|
39
|
+
flushIntervalMs: resolved.flushIntervalMs,
|
|
40
|
+
});
|
|
41
|
+
shipper.start();
|
|
42
|
+
// The exit handler is synchronous on purpose: an async flush cannot
|
|
43
|
+
// run during `exit`, so the remaining records go to the local spool.
|
|
44
|
+
process.once("exit", () => {
|
|
45
|
+
shipper?.flushSync();
|
|
46
|
+
});
|
|
47
|
+
return resolved;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** The active shipper, or null when logging is not initialized. */
|
|
54
|
+
export function getCentralShipper() {
|
|
55
|
+
return shipper;
|
|
56
|
+
}
|
|
57
|
+
/** The resolved configuration, or null when logging was never initialized. */
|
|
58
|
+
export function getCentralLogConfig() {
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
/** Emit one record. No-op when the pipeline is not initialized. */
|
|
62
|
+
export function centralLog(level, source, event, message, fields, error) {
|
|
63
|
+
try {
|
|
64
|
+
shipper?.log({ level, source, event, message, fields, error });
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Never let logging break the caller.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function centralDebug(source, event, message, fields) {
|
|
71
|
+
centralLog("debug", source, event, message, fields);
|
|
72
|
+
}
|
|
73
|
+
export function centralInfo(source, event, message, fields) {
|
|
74
|
+
centralLog("info", source, event, message, fields);
|
|
75
|
+
}
|
|
76
|
+
export function centralWarn(source, event, message, fields) {
|
|
77
|
+
centralLog("warn", source, event, message, fields);
|
|
78
|
+
}
|
|
79
|
+
export function centralError(source, event, message, fields, error) {
|
|
80
|
+
centralLog("error", source, event, message, fields, error);
|
|
81
|
+
}
|
|
82
|
+
/** Flush and stop the pipeline. Safe to call when it was never started. */
|
|
83
|
+
export async function shutdownCentralLogging() {
|
|
84
|
+
if (!shipper)
|
|
85
|
+
return;
|
|
86
|
+
const current = shipper;
|
|
87
|
+
shipper = null;
|
|
88
|
+
try {
|
|
89
|
+
await current.stop();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Nothing to do at shutdown time.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=singleton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../src/singleton.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local NDJSON spool - the last-resort destination for log batches.
|
|
3
|
+
*
|
|
4
|
+
* When every transport fails (no network, R2 down, webhook server down) the
|
|
5
|
+
* batch is written to disk instead of being lost. The spool is drained on
|
|
6
|
+
* the next successful flush, so a temporary outage does not cost data.
|
|
7
|
+
*
|
|
8
|
+
* Files are plain NDJSON, one record per line, so an operator can inspect
|
|
9
|
+
* them with `tail`, `jq` or `less` without any tooling.
|
|
10
|
+
*/
|
|
11
|
+
import type { LogBatch, LogRecord } from "./types.js";
|
|
12
|
+
export interface LogSpoolOptions {
|
|
13
|
+
/** Directory that holds the spool files. */
|
|
14
|
+
dir: string;
|
|
15
|
+
/** Rotate a file when it would exceed this size. Default 5 MB. */
|
|
16
|
+
maxFileBytes?: number;
|
|
17
|
+
/** Maximum number of spool files kept. Default 20. */
|
|
18
|
+
maxFiles?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare class LogSpool {
|
|
21
|
+
private readonly dir;
|
|
22
|
+
private readonly maxFileBytes;
|
|
23
|
+
private readonly maxFiles;
|
|
24
|
+
constructor(options: LogSpoolOptions);
|
|
25
|
+
/** Append a batch to the spool. Returns the file that was written. */
|
|
26
|
+
append(batch: LogBatch, now?: Date): string;
|
|
27
|
+
/** Spool files, oldest first. */
|
|
28
|
+
list(): string[];
|
|
29
|
+
/** Read the records of a spool file. */
|
|
30
|
+
read(file: string): LogRecord[];
|
|
31
|
+
/** Delete a spool file after it was shipped. */
|
|
32
|
+
remove(file: string): void;
|
|
33
|
+
private ensureDir;
|
|
34
|
+
private currentFile;
|
|
35
|
+
private shouldRotate;
|
|
36
|
+
private rotate;
|
|
37
|
+
private enforceRetention;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=spool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spool.d.ts","sourceRoot":"","sources":["../src/spool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,YAAY,OAAO,EAAE,eAAe,EAInC;IAED,sEAAsE;IACtE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAYtD;IAED,iCAAiC;IACjC,IAAI,IAAI,MAAM,EAAE,CAUf;IAED,wCAAwC;IACxC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAc9B;IAED,gDAAgD;IAChD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMzB;IAED,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,gBAAgB;CAOxB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local NDJSON spool - the last-resort destination for log batches.
|
|
3
|
+
*
|
|
4
|
+
* When every transport fails (no network, R2 down, webhook server down) the
|
|
5
|
+
* batch is written to disk instead of being lost. The spool is drained on
|
|
6
|
+
* the next successful flush, so a temporary outage does not cost data.
|
|
7
|
+
*
|
|
8
|
+
* Files are plain NDJSON, one record per line, so an operator can inspect
|
|
9
|
+
* them with `tail`, `jq` or `less` without any tooling.
|
|
10
|
+
*/
|
|
11
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
const FILE_PREFIX = "logs-spool-";
|
|
14
|
+
const FILE_SUFFIX = ".ndjson";
|
|
15
|
+
export class LogSpool {
|
|
16
|
+
dir;
|
|
17
|
+
maxFileBytes;
|
|
18
|
+
maxFiles;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.dir = options.dir;
|
|
21
|
+
this.maxFileBytes = Math.max(1024, options.maxFileBytes ?? 5 * 1024 * 1024);
|
|
22
|
+
this.maxFiles = Math.max(1, options.maxFiles ?? 20);
|
|
23
|
+
}
|
|
24
|
+
/** Append a batch to the spool. Returns the file that was written. */
|
|
25
|
+
append(batch, now = new Date()) {
|
|
26
|
+
this.ensureDir();
|
|
27
|
+
const file = this.currentFile(now);
|
|
28
|
+
const payload = batch.records.map((record) => JSON.stringify(record)).join("\n") + "\n";
|
|
29
|
+
if (this.shouldRotate(file, payload.length)) {
|
|
30
|
+
this.rotate(file);
|
|
31
|
+
}
|
|
32
|
+
appendFileSync(file, payload, "utf8");
|
|
33
|
+
this.enforceRetention();
|
|
34
|
+
return file;
|
|
35
|
+
}
|
|
36
|
+
/** Spool files, oldest first. */
|
|
37
|
+
list() {
|
|
38
|
+
if (!existsSync(this.dir))
|
|
39
|
+
return [];
|
|
40
|
+
return readdirSync(this.dir)
|
|
41
|
+
.filter((name) => name.startsWith(FILE_PREFIX) &&
|
|
42
|
+
(name.endsWith(FILE_SUFFIX) || new RegExp(`\\${FILE_SUFFIX}\\.\\d+$`).test(name)))
|
|
43
|
+
.map((name) => join(this.dir, name))
|
|
44
|
+
.sort();
|
|
45
|
+
}
|
|
46
|
+
/** Read the records of a spool file. */
|
|
47
|
+
read(file) {
|
|
48
|
+
if (!existsSync(file))
|
|
49
|
+
return [];
|
|
50
|
+
const content = readFileSync(file, "utf8");
|
|
51
|
+
const records = [];
|
|
52
|
+
for (const line of content.split("\n")) {
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
if (!trimmed)
|
|
55
|
+
continue;
|
|
56
|
+
try {
|
|
57
|
+
records.push(JSON.parse(trimmed));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// A truncated last line is expected after a crash; skip it.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return records;
|
|
64
|
+
}
|
|
65
|
+
/** Delete a spool file after it was shipped. */
|
|
66
|
+
remove(file) {
|
|
67
|
+
try {
|
|
68
|
+
rmSync(file, { force: true });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Nothing to do - the file stays and is retried later.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
ensureDir() {
|
|
75
|
+
if (!existsSync(this.dir)) {
|
|
76
|
+
mkdirSync(this.dir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
currentFile(now) {
|
|
80
|
+
const day = now.toISOString().slice(0, 10);
|
|
81
|
+
return join(this.dir, `${FILE_PREFIX}${day}${FILE_SUFFIX}`);
|
|
82
|
+
}
|
|
83
|
+
shouldRotate(file, incomingBytes) {
|
|
84
|
+
if (!existsSync(file))
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
return statSync(file).size + incomingBytes > this.maxFileBytes;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
rotate(file) {
|
|
94
|
+
for (let index = 1; index <= this.maxFiles; index++) {
|
|
95
|
+
const candidate = `${file}.${index}`;
|
|
96
|
+
if (!existsSync(candidate)) {
|
|
97
|
+
renameSync(file, candidate);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
enforceRetention() {
|
|
103
|
+
const files = this.list();
|
|
104
|
+
const extra = files.length - this.maxFiles;
|
|
105
|
+
for (let i = 0; i < extra; i++) {
|
|
106
|
+
this.remove(files[i]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=spool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spool.js","sourceRoot":"","sources":["../src/spool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACN,cAAc,EACd,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,MAAM,EACN,QAAQ,GACR,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAYjC,MAAM,WAAW,GAAG,aAAa,CAAC;AAClC,MAAM,WAAW,GAAG,SAAS,CAAC;AAE9B,MAAM,OAAO,QAAQ;IACH,GAAG,CAAS;IACZ,YAAY,CAAS;IACrB,QAAQ,CAAS;IAElC,YAAY,OAAwB;QACnC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;QAC5E,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,sEAAsE;IACtE,MAAM,CAAC,KAAe,EAAE,GAAG,GAAS,IAAI,IAAI,EAAE;QAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAExF,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QAED,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,iCAAiC;IACjC,IAAI;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACrC,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;aAC1B,MAAM,CACN,CAAC,IAAI,EAAE,EAAE,CACR,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAC5B,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,WAAW,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAClF;aACA,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;aACnC,IAAI,EAAE,CAAC;IACV,CAAC;IAED,wCAAwC;IACxC,IAAI,CAAC,IAAY;QAChB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAgB,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,IAAI,CAAC;gBACJ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAc,CAAC,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC;gBACR,4DAA4D;YAC7D,CAAC;QACF,CAAC;QACD,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,gDAAgD;IAChD,MAAM,CAAC,IAAY;QAClB,IAAI,CAAC;YACJ,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACR,uDAAuD;QACxD,CAAC;IACF,CAAC;IAEO,SAAS;QAChB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;IAEO,WAAW,CAAC,GAAS;QAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,GAAG,GAAG,GAAG,WAAW,EAAE,CAAC,CAAC;IAC7D,CAAC;IAEO,YAAY,CAAC,IAAY,EAAE,aAAqB;QACvD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACpC,IAAI,CAAC;YACJ,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC;QAChE,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAEO,MAAM,CAAC,IAAY;QAC1B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC;YACrD,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;YACrC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC5B,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBAC5B,OAAO;YACR,CAAC;QACF,CAAC;IACF,CAAC;IAEO,gBAAgB;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;CACD"}
|