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,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare R2 transport.
|
|
3
|
+
*
|
|
4
|
+
* Batches are uploaded as NDJSON objects under a date-partitioned prefix so
|
|
5
|
+
* a bucket can be queried by day without listing every object:
|
|
6
|
+
*
|
|
7
|
+
* logs/harness/2026/10/24/09/20261024T091500-abc123.ndjson.gz
|
|
8
|
+
*
|
|
9
|
+
* The AWS SDK is imported lazily so the package stays usable (and testable)
|
|
10
|
+
* without the SDK installed, and so a missing dependency surfaces as a
|
|
11
|
+
* transport error instead of a crash at startup.
|
|
12
|
+
*/
|
|
13
|
+
import type { LogBatch, LogTransport } from "../types.js";
|
|
14
|
+
export interface R2TransportOptions {
|
|
15
|
+
/** R2 bucket name. */
|
|
16
|
+
bucket: string;
|
|
17
|
+
/** R2 endpoint, e.g. https://<account>.r2.cloudflarestorage.com */
|
|
18
|
+
endpoint: string;
|
|
19
|
+
accessKeyId: string;
|
|
20
|
+
secretAccessKey: string;
|
|
21
|
+
/** Key prefix inside the bucket. Default "logs/harness". */
|
|
22
|
+
prefix?: string;
|
|
23
|
+
/** Region label; R2 uses "auto". Default "auto". */
|
|
24
|
+
region?: string;
|
|
25
|
+
/** gzip the payload. Default true. */
|
|
26
|
+
gzip?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Build the date-partitioned object key for a batch. */
|
|
29
|
+
export declare function r2ObjectKey(prefix: string, batch: LogBatch): string;
|
|
30
|
+
export declare function createR2Transport(options: R2TransportOptions): LogTransport;
|
|
31
|
+
//# sourceMappingURL=r2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../../src/transports/r2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IAClC,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,CAOnE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,YAAY,CA4C3E"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare R2 transport.
|
|
3
|
+
*
|
|
4
|
+
* Batches are uploaded as NDJSON objects under a date-partitioned prefix so
|
|
5
|
+
* a bucket can be queried by day without listing every object:
|
|
6
|
+
*
|
|
7
|
+
* logs/harness/2026/10/24/09/20261024T091500-abc123.ndjson.gz
|
|
8
|
+
*
|
|
9
|
+
* The AWS SDK is imported lazily so the package stays usable (and testable)
|
|
10
|
+
* without the SDK installed, and so a missing dependency surfaces as a
|
|
11
|
+
* transport error instead of a crash at startup.
|
|
12
|
+
*/
|
|
13
|
+
import { gzipSync } from "node:zlib";
|
|
14
|
+
/** Build the date-partitioned object key for a batch. */
|
|
15
|
+
export function r2ObjectKey(prefix, batch) {
|
|
16
|
+
const sentAt = new Date(batch.sentAt);
|
|
17
|
+
const year = sentAt.getUTCFullYear();
|
|
18
|
+
const month = String(sentAt.getUTCMonth() + 1).padStart(2, "0");
|
|
19
|
+
const day = String(sentAt.getUTCDate()).padStart(2, "0");
|
|
20
|
+
const hour = String(sentAt.getUTCHours()).padStart(2, "0");
|
|
21
|
+
return `${prefix}/${year}/${month}/${day}/${hour}/${batch.batchId}.ndjson`;
|
|
22
|
+
}
|
|
23
|
+
export function createR2Transport(options) {
|
|
24
|
+
const prefix = options.prefix ?? "logs/harness";
|
|
25
|
+
const region = options.region ?? "auto";
|
|
26
|
+
const gzip = options.gzip ?? true;
|
|
27
|
+
return {
|
|
28
|
+
name: "r2",
|
|
29
|
+
async send(batch) {
|
|
30
|
+
if (batch.records.length === 0)
|
|
31
|
+
return;
|
|
32
|
+
let s3;
|
|
33
|
+
try {
|
|
34
|
+
s3 = await import("@aws-sdk/client-s3");
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`R2 transport requires @aws-sdk/client-s3: ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
+
}
|
|
39
|
+
const client = new s3.S3Client({
|
|
40
|
+
region,
|
|
41
|
+
endpoint: options.endpoint,
|
|
42
|
+
credentials: {
|
|
43
|
+
accessKeyId: options.accessKeyId,
|
|
44
|
+
secretAccessKey: options.secretAccessKey,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const payload = batch.records.map((record) => JSON.stringify(record)).join("\n") + "\n";
|
|
48
|
+
const body = gzip ? gzipSync(payload) : payload;
|
|
49
|
+
const key = `${r2ObjectKey(prefix, batch)}${gzip ? ".gz" : ""}`;
|
|
50
|
+
await client.send(new s3.PutObjectCommand({
|
|
51
|
+
Bucket: options.bucket,
|
|
52
|
+
Key: key,
|
|
53
|
+
Body: body,
|
|
54
|
+
ContentType: gzip ? "application/gzip" : "application/x-ndjson",
|
|
55
|
+
}));
|
|
56
|
+
client.destroy();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=r2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.js","sourceRoot":"","sources":["../../src/transports/r2.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook transport.
|
|
3
|
+
*
|
|
4
|
+
* Delivers batches over HTTP to the existing unified webhook server
|
|
5
|
+
* (`POST /api/logs`). This is the low-latency path: the server can fan the
|
|
6
|
+
* batch out to alerting, while R2 keeps the durable archive.
|
|
7
|
+
*/
|
|
8
|
+
import type { LogTransport } from "../types.js";
|
|
9
|
+
export interface WebhookTransportOptions {
|
|
10
|
+
/** Full URL of the ingest endpoint. */
|
|
11
|
+
url: string;
|
|
12
|
+
/** Optional bearer token. */
|
|
13
|
+
token?: string;
|
|
14
|
+
/** Extra headers. */
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
/** Request timeout in milliseconds. Default 10000. */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
/** Custom fetch implementation (tests). */
|
|
19
|
+
fetchImpl?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
export declare function createWebhookTransport(options: WebhookTransportOptions): LogTransport;
|
|
22
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/transports/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,uBAAuB;IACvC,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CACzB;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,YAAY,CA8BrF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook transport.
|
|
3
|
+
*
|
|
4
|
+
* Delivers batches over HTTP to the existing unified webhook server
|
|
5
|
+
* (`POST /api/logs`). This is the low-latency path: the server can fan the
|
|
6
|
+
* batch out to alerting, while R2 keeps the durable archive.
|
|
7
|
+
*/
|
|
8
|
+
export function createWebhookTransport(options) {
|
|
9
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
10
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
11
|
+
return {
|
|
12
|
+
name: "webhook",
|
|
13
|
+
async send(batch) {
|
|
14
|
+
if (batch.records.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
if (typeof fetchImpl !== "function") {
|
|
17
|
+
throw new Error("Webhook transport requires a fetch implementation");
|
|
18
|
+
}
|
|
19
|
+
const headers = {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
...options.headers,
|
|
22
|
+
};
|
|
23
|
+
if (options.token)
|
|
24
|
+
headers.Authorization = `Bearer ${options.token}`;
|
|
25
|
+
const response = await fetchImpl(options.url, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers,
|
|
28
|
+
body: JSON.stringify(batch),
|
|
29
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`Webhook transport failed with HTTP ${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/transports/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiBH,MAAM,UAAU,sBAAsB,CAAC,OAAgC;IACtE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC9C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC;IAExD,OAAO;QACN,IAAI,EAAE,SAAS;QACf,KAAK,CAAC,IAAI,CAAC,KAAe;YACzB,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YACvC,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;gBACrC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;YACtE,CAAC;YAED,MAAM,OAAO,GAA2B;gBACvC,cAAc,EAAE,kBAAkB;gBAClC,GAAG,OAAO,CAAC,OAAO;aAClB,CAAC;YACF,IAAI,OAAO,CAAC,KAAK;gBAAE,OAAO,CAAC,aAAa,GAAG,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC;YAErE,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC7C,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;gBAC3B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACtC,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,sCAAsC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAC1E,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Sink - shared types for the centralized logging pipeline.
|
|
3
|
+
*
|
|
4
|
+
* The pipeline has three layers:
|
|
5
|
+
* 1. `LogRecord` - the structured, redacted unit that every producer emits.
|
|
6
|
+
* 2. `LogBatch` - the transport unit (NDJSON over R2 or HTTP to the webhook).
|
|
7
|
+
* 3. `LogTransport` - the pluggable delivery mechanism.
|
|
8
|
+
*
|
|
9
|
+
* Producers never know which transport is configured; the shipper owns that
|
|
10
|
+
* decision, including the fallback order and the local spool.
|
|
11
|
+
*/
|
|
12
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
13
|
+
/** Severity order used by the sampler and the buffer drop policy. */
|
|
14
|
+
export declare const LOG_LEVEL_SEVERITY: Record<LogLevel, number>;
|
|
15
|
+
export interface LogErrorInfo {
|
|
16
|
+
name: string;
|
|
17
|
+
message: string;
|
|
18
|
+
stack?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A single structured log record. */
|
|
21
|
+
export interface LogRecord {
|
|
22
|
+
/** ISO-8601 timestamp of the event. */
|
|
23
|
+
ts: string;
|
|
24
|
+
level: LogLevel;
|
|
25
|
+
/** Emitting component, e.g. "jev-judge" or "harness.service-diagnostics". */
|
|
26
|
+
source: string;
|
|
27
|
+
/** Dotted event name, e.g. "service.config_checked". */
|
|
28
|
+
event: string;
|
|
29
|
+
message?: string;
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
durationMs?: number;
|
|
32
|
+
/** Structured, already redacted payload. */
|
|
33
|
+
fields?: Record<string, unknown>;
|
|
34
|
+
error?: LogErrorInfo;
|
|
35
|
+
}
|
|
36
|
+
/** What a producer hands to the shipper. */
|
|
37
|
+
export interface LogInput {
|
|
38
|
+
level: LogLevel;
|
|
39
|
+
source: string;
|
|
40
|
+
event: string;
|
|
41
|
+
message?: string;
|
|
42
|
+
sessionId?: string;
|
|
43
|
+
durationMs?: number;
|
|
44
|
+
fields?: Record<string, unknown>;
|
|
45
|
+
/** Anything thrown - converted to a `LogErrorInfo` by the shipper. */
|
|
46
|
+
error?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/** The transport unit: a batch of records shipped together. */
|
|
49
|
+
export interface LogBatch {
|
|
50
|
+
batchId: string;
|
|
51
|
+
app: string;
|
|
52
|
+
host: string;
|
|
53
|
+
sentAt: string;
|
|
54
|
+
records: LogRecord[];
|
|
55
|
+
}
|
|
56
|
+
/** A delivery mechanism. Throwing means "delivery failed". */
|
|
57
|
+
export interface LogTransport {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
send(batch: LogBatch): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export interface FlushStats {
|
|
62
|
+
/** Records handed to a transport successfully. */
|
|
63
|
+
shipped: number;
|
|
64
|
+
/** Records written to the local spool after every transport failed. */
|
|
65
|
+
spooled: number;
|
|
66
|
+
/** Records dropped by the buffer because it was full. */
|
|
67
|
+
dropped: number;
|
|
68
|
+
/** Records dropped by the sampler (level, sample rate, rate limit). */
|
|
69
|
+
sampled: number;
|
|
70
|
+
/** Transport errors, keyed by transport name. */
|
|
71
|
+
errors: Record<string, string>;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAKvD,CAAC;AAEF,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACzB,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,QAAQ,CAAC;IAChB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,YAAY,CAAC;CACrB;AAED,4CAA4C;AAC5C,MAAM,WAAW,QAAQ;IACxB,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,sEAAsE;IACtE,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,UAAU;IAC1B,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Sink - shared types for the centralized logging pipeline.
|
|
3
|
+
*
|
|
4
|
+
* The pipeline has three layers:
|
|
5
|
+
* 1. `LogRecord` - the structured, redacted unit that every producer emits.
|
|
6
|
+
* 2. `LogBatch` - the transport unit (NDJSON over R2 or HTTP to the webhook).
|
|
7
|
+
* 3. `LogTransport` - the pluggable delivery mechanism.
|
|
8
|
+
*
|
|
9
|
+
* Producers never know which transport is configured; the shipper owns that
|
|
10
|
+
* decision, including the fallback order and the local spool.
|
|
11
|
+
*/
|
|
12
|
+
/** Severity order used by the sampler and the buffer drop policy. */
|
|
13
|
+
export const LOG_LEVEL_SEVERITY = {
|
|
14
|
+
debug: 10,
|
|
15
|
+
info: 20,
|
|
16
|
+
warn: 30,
|
|
17
|
+
error: 40,
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAA6B;IAC3D,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;CACT,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-harness/logging",
|
|
3
|
+
"version": "1.1.396",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Centralized structured application logging with buffered Cloudflare R2 and webhook transports",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "bun test"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"logging",
|
|
20
|
+
"observability",
|
|
21
|
+
"r2",
|
|
22
|
+
"cloudflare"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^26.6.4",
|
|
27
|
+
"typescript": "^5.7.2"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@aws-sdk/client-s3": ">=3.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"@aws-sdk/client-s3": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory batching buffer for the centralized log pipeline.
|
|
3
|
+
*
|
|
4
|
+
* The buffer is bounded on purpose: a logging subsystem must never be the
|
|
5
|
+
* reason a process runs out of memory. When the buffer is full the LEAST
|
|
6
|
+
* severe record is dropped first, and the drop is counted so the loss is
|
|
7
|
+
* visible instead of silent.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { LOG_LEVEL_SEVERITY, type LogRecord } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export interface LogBufferOptions {
|
|
13
|
+
/** Maximum number of records held in memory. Default 1000. */
|
|
14
|
+
maxSize?: number;
|
|
15
|
+
/** Maximum number of records per batch. Default 100. */
|
|
16
|
+
maxBatchSize?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class LogBuffer {
|
|
20
|
+
private readonly records: LogRecord[] = [];
|
|
21
|
+
private readonly maxSize: number;
|
|
22
|
+
private readonly maxBatchSize: number;
|
|
23
|
+
private droppedCount = 0;
|
|
24
|
+
|
|
25
|
+
constructor(options: LogBufferOptions = {}) {
|
|
26
|
+
this.maxSize = Math.max(1, options.maxSize ?? 1000);
|
|
27
|
+
this.maxBatchSize = Math.max(1, options.maxBatchSize ?? 100);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get size(): number {
|
|
31
|
+
return this.records.length;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get dropped(): number {
|
|
35
|
+
return this.droppedCount;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get batchSize(): number {
|
|
39
|
+
return this.maxBatchSize;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
push(record: LogRecord): void {
|
|
43
|
+
if (this.records.length >= this.maxSize) {
|
|
44
|
+
this.dropLowestSeverity();
|
|
45
|
+
}
|
|
46
|
+
this.records.push(record);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Remove and return up to `maxBatchSize` of the oldest records. */
|
|
50
|
+
takeBatch(maxBatchSize: number = this.maxBatchSize): LogRecord[] {
|
|
51
|
+
const count = Math.min(Math.max(0, maxBatchSize), this.records.length);
|
|
52
|
+
return this.records.splice(0, count);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Peek without removing (used by the synchronous exit path). */
|
|
56
|
+
peekAll(): LogRecord[] {
|
|
57
|
+
return [...this.records];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clear(): void {
|
|
61
|
+
this.records.length = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private dropLowestSeverity(): void {
|
|
65
|
+
let index = 0;
|
|
66
|
+
let lowest = Number.POSITIVE_INFINITY;
|
|
67
|
+
for (let i = 0; i < this.records.length; i++) {
|
|
68
|
+
const severity = LOG_LEVEL_SEVERITY[this.records[i].level];
|
|
69
|
+
if (severity < lowest) {
|
|
70
|
+
lowest = severity;
|
|
71
|
+
index = i;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.records.splice(index, 1);
|
|
75
|
+
this.droppedCount++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the centralized log pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order for credentials:
|
|
5
|
+
* environment variable -> key file in ~/.pi-harness-runtime/keys
|
|
6
|
+
*
|
|
7
|
+
* Secrets are never hardcoded here. A key file that is missing simply means
|
|
8
|
+
* the corresponding transport is not configured.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { createR2Transport } from "./transports/r2.js";
|
|
15
|
+
import { createWebhookTransport } from "./transports/webhook.js";
|
|
16
|
+
import type { LogBufferOptions } from "./buffer.js";
|
|
17
|
+
import type { SamplingPolicy } from "./sampler.js";
|
|
18
|
+
import type { LogTransport } from "./types.js";
|
|
19
|
+
|
|
20
|
+
export type LogSinkMode = "off" | "auto" | "r2" | "webhook" | "r2+webhook";
|
|
21
|
+
|
|
22
|
+
export interface LogSinkConfig {
|
|
23
|
+
/** False when nothing should be shipped (no transports, no spool). */
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
/** Application name reported with every batch. */
|
|
26
|
+
app: string;
|
|
27
|
+
/** Transports in delivery priority order. */
|
|
28
|
+
transports: LogTransport[];
|
|
29
|
+
/** Directory of the local spool (undefined when logging is off). */
|
|
30
|
+
spoolDir?: string;
|
|
31
|
+
sampling: Partial<SamplingPolicy>;
|
|
32
|
+
buffer: LogBufferOptions;
|
|
33
|
+
flushIntervalMs: number;
|
|
34
|
+
/** Human readable notes about what was enabled and why. */
|
|
35
|
+
notes: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ResolveConfigOptions {
|
|
39
|
+
env?: Record<string, string | undefined>;
|
|
40
|
+
/** Directory holding the credential key files. */
|
|
41
|
+
keysDir?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readKeyFile(keysDir: string, name: string): string | undefined {
|
|
45
|
+
const file = join(keysDir, name);
|
|
46
|
+
if (!existsSync(file)) return undefined;
|
|
47
|
+
const value = readFileSync(file, "utf8").trim();
|
|
48
|
+
return value.length > 0 ? value : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readNumber(
|
|
52
|
+
env: Record<string, string | undefined>,
|
|
53
|
+
key: string,
|
|
54
|
+
fallback: number,
|
|
55
|
+
): number {
|
|
56
|
+
const raw = env[key];
|
|
57
|
+
if (!raw) return fallback;
|
|
58
|
+
const parsed = Number(raw);
|
|
59
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function defaultKeysDir(): string {
|
|
63
|
+
return join(homedir(), ".pi-harness-runtime", "keys");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function defaultSpoolDir(): string {
|
|
67
|
+
return join(homedir(), ".pi-harness-runtime", "logs", "spool");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build the log sink configuration from the environment and the key files.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveLogSinkConfig(options: ResolveConfigOptions = {}): LogSinkConfig {
|
|
74
|
+
const env = options.env ?? process.env;
|
|
75
|
+
const keysDir = options.keysDir ?? defaultKeysDir();
|
|
76
|
+
const mode = (env.PI_LOG_SINK ?? "auto") as LogSinkMode;
|
|
77
|
+
const notes: string[] = [];
|
|
78
|
+
|
|
79
|
+
if (mode === "off") {
|
|
80
|
+
return {
|
|
81
|
+
enabled: false,
|
|
82
|
+
app: env.PI_LOG_APP ?? "pi-harness",
|
|
83
|
+
transports: [],
|
|
84
|
+
sampling: {},
|
|
85
|
+
buffer: {},
|
|
86
|
+
flushIntervalMs: 0,
|
|
87
|
+
notes: ["logging disabled by PI_LOG_SINK=off"],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const transports: LogTransport[] = [];
|
|
92
|
+
|
|
93
|
+
const bucket = env.PI_LOG_R2_BUCKET ?? readKeyFile(keysDir, "r2-bucket-name.txt");
|
|
94
|
+
const accessKeyId =
|
|
95
|
+
env.PI_LOG_R2_ACCESS_KEY_ID ?? readKeyFile(keysDir, "r2-access-key-id.txt");
|
|
96
|
+
const secretAccessKey =
|
|
97
|
+
env.PI_LOG_R2_SECRET_ACCESS_KEY ?? readKeyFile(keysDir, "r2-secret-access-key.txt");
|
|
98
|
+
const accountId = env.PI_LOG_R2_ACCOUNT_ID ?? readKeyFile(keysDir, "r2-account-id.txt");
|
|
99
|
+
const endpoint =
|
|
100
|
+
env.PI_LOG_R2_ENDPOINT ??
|
|
101
|
+
readKeyFile(keysDir, "r2-endpoint.txt") ??
|
|
102
|
+
(accountId ? `https://${accountId}.r2.cloudflarestorage.com` : undefined);
|
|
103
|
+
|
|
104
|
+
const r2Ready = Boolean(bucket && accessKeyId && secretAccessKey && endpoint);
|
|
105
|
+
const wantR2 = mode === "r2" || mode === "r2+webhook" || (mode === "auto" && r2Ready);
|
|
106
|
+
|
|
107
|
+
if (wantR2) {
|
|
108
|
+
if (r2Ready) {
|
|
109
|
+
transports.push(
|
|
110
|
+
createR2Transport({
|
|
111
|
+
bucket: bucket as string,
|
|
112
|
+
endpoint: endpoint as string,
|
|
113
|
+
accessKeyId: accessKeyId as string,
|
|
114
|
+
secretAccessKey: secretAccessKey as string,
|
|
115
|
+
prefix: env.PI_LOG_R2_PREFIX ?? "logs/harness",
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
notes.push(`r2 enabled (bucket ${bucket})`);
|
|
119
|
+
} else {
|
|
120
|
+
notes.push("r2 requested but credentials are incomplete - transport skipped");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const webhookUrl = env.PI_LOG_WEBHOOK_URL;
|
|
125
|
+
const wantWebhook =
|
|
126
|
+
mode === "webhook" || mode === "r2+webhook" || (mode === "auto" && Boolean(webhookUrl));
|
|
127
|
+
|
|
128
|
+
if (wantWebhook) {
|
|
129
|
+
if (webhookUrl) {
|
|
130
|
+
transports.push(
|
|
131
|
+
createWebhookTransport({
|
|
132
|
+
url: webhookUrl,
|
|
133
|
+
token: env.PI_LOG_WEBHOOK_TOKEN,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
notes.push(`webhook enabled (${webhookUrl})`);
|
|
137
|
+
} else {
|
|
138
|
+
notes.push("webhook requested but PI_LOG_WEBHOOK_URL is not set - transport skipped");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (transports.length === 0) {
|
|
143
|
+
notes.push("no transport configured - logs stay local");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
enabled: true,
|
|
148
|
+
app: env.PI_LOG_APP ?? "pi-harness",
|
|
149
|
+
transports,
|
|
150
|
+
spoolDir: env.PI_LOG_SPOOL_DIR ?? defaultSpoolDir(),
|
|
151
|
+
sampling: {
|
|
152
|
+
minLevel: (env.PI_LOG_LEVEL as SamplingPolicy["minLevel"]) ?? "info",
|
|
153
|
+
sampleRate: readNumber(env, "PI_LOG_SAMPLE_RATE", 0.1),
|
|
154
|
+
rateLimitPerMinute: readNumber(env, "PI_LOG_RATE_LIMIT", 60),
|
|
155
|
+
},
|
|
156
|
+
buffer: {
|
|
157
|
+
maxSize: readNumber(env, "PI_LOG_BUFFER_SIZE", 1000),
|
|
158
|
+
maxBatchSize: readNumber(env, "PI_LOG_BATCH_SIZE", 100),
|
|
159
|
+
},
|
|
160
|
+
flushIntervalMs: readNumber(env, "PI_LOG_FLUSH_INTERVAL_MS", 15_000),
|
|
161
|
+
notes,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-harness/logging - centralized application logging.
|
|
3
|
+
*
|
|
4
|
+
* One structured record format, one redaction policy, one delivery path:
|
|
5
|
+
*
|
|
6
|
+
* producer -> redactor -> sampler -> buffer -> R2 / webhook -> local spool
|
|
7
|
+
*
|
|
8
|
+
* The transport order is deliberate: R2 keeps the durable archive, the
|
|
9
|
+
* webhook server provides the live path, and the local spool is the
|
|
10
|
+
* fallback that keeps data when both are unavailable.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// --- Types -----------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export { LOG_LEVEL_SEVERITY } from "./types.js";
|
|
16
|
+
export type {
|
|
17
|
+
FlushStats,
|
|
18
|
+
LogBatch,
|
|
19
|
+
LogErrorInfo,
|
|
20
|
+
LogInput,
|
|
21
|
+
LogLevel,
|
|
22
|
+
LogRecord,
|
|
23
|
+
LogTransport,
|
|
24
|
+
} from "./types.js";
|
|
25
|
+
|
|
26
|
+
// --- Redaction -------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
isSecretKey,
|
|
30
|
+
isSensitiveValueKey,
|
|
31
|
+
redactFields,
|
|
32
|
+
redactText,
|
|
33
|
+
redactValue,
|
|
34
|
+
splitKeyTokens,
|
|
35
|
+
} from "./redact.js";
|
|
36
|
+
export type { JsonValue, RedactionOptions } from "./redact.js";
|
|
37
|
+
|
|
38
|
+
// --- Sampling --------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
export { DEFAULT_SAMPLING_POLICY, LogSampler } from "./sampler.js";
|
|
41
|
+
export type { SamplingPolicy } from "./sampler.js";
|
|
42
|
+
|
|
43
|
+
// --- Buffering -------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
export { LogBuffer } from "./buffer.js";
|
|
46
|
+
export type { LogBufferOptions } from "./buffer.js";
|
|
47
|
+
|
|
48
|
+
// --- Local spool -----------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
export { LogSpool } from "./spool.js";
|
|
51
|
+
export type { LogSpoolOptions } from "./spool.js";
|
|
52
|
+
|
|
53
|
+
// --- Transports ------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
export { createR2Transport, r2ObjectKey } from "./transports/r2.js";
|
|
56
|
+
export type { R2TransportOptions } from "./transports/r2.js";
|
|
57
|
+
export { createWebhookTransport } from "./transports/webhook.js";
|
|
58
|
+
export type { WebhookTransportOptions } from "./transports/webhook.js";
|
|
59
|
+
|
|
60
|
+
// --- Shipper ---------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
export { LogShipper } from "./shipper.js";
|
|
63
|
+
export type { LogShipperOptions } from "./shipper.js";
|
|
64
|
+
|
|
65
|
+
// --- Process wide singleton -------------------------------------------------
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
centralDebug,
|
|
69
|
+
centralError,
|
|
70
|
+
centralInfo,
|
|
71
|
+
centralLog,
|
|
72
|
+
centralWarn,
|
|
73
|
+
getCentralLogConfig,
|
|
74
|
+
getCentralShipper,
|
|
75
|
+
initCentralLogging,
|
|
76
|
+
shutdownCentralLogging,
|
|
77
|
+
} from "./singleton.js";
|
|
78
|
+
export type { InitCentralLoggingOptions } from "./singleton.js";
|
|
79
|
+
|
|
80
|
+
// --- Configuration ---------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
defaultKeysDir,
|
|
84
|
+
defaultSpoolDir,
|
|
85
|
+
resolveLogSinkConfig,
|
|
86
|
+
} from "./config.js";
|
|
87
|
+
export type { LogSinkConfig, LogSinkMode, ResolveConfigOptions } from "./config.js";
|