@camstack/system 1.2.456 → 1.2.457
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/dist/addon-runner.js +96 -10
- package/dist/addon-runner.mjs +96 -10
- package/dist/{agent-https-fm4AR8XL.mjs → agent-https-CK9Z4sSR.mjs} +2 -2
- package/dist/{agent-https-yjxdfJDA.js → agent-https-CoyOZ3pC.js} +2 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +2 -2
- package/dist/builtins/alerts/alerts.addon.mjs +2 -2
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/console-destination.d.ts +1 -2
- package/dist/builtins/console-logging/index.js +1 -4
- package/dist/builtins/console-logging/index.mjs +1 -4
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-history/capture/capture-engine.d.ts +6 -0
- package/dist/builtins/device-history/device-history.addon.js +441 -83
- package/dist/builtins/device-history/device-history.addon.mjs +441 -83
- package/dist/builtins/device-history/query/history-query.d.ts +35 -9
- package/dist/builtins/device-history/query/point-shape.d.ts +33 -0
- package/dist/builtins/device-history/retention/retention.d.ts +1 -1
- package/dist/builtins/device-history/rollup/rollup-engine.d.ts +2 -0
- package/dist/builtins/device-history/rollup/rollup-math.d.ts +6 -0
- package/dist/builtins/device-history/store/history-store.d.ts +7 -0
- package/dist/builtins/device-history/store/series-registry.d.ts +26 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +1 -2
- package/dist/builtins/hub-forwarder/index.js +1 -4
- package/dist/builtins/hub-forwarder/index.mjs +1 -4
- package/dist/builtins/keychain/index.js +4 -4
- package/dist/builtins/keychain/index.mjs +4 -4
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +3 -3
- package/dist/builtins/local-auth/local-auth.addon.mjs +3 -3
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- package/dist/builtins/loki-logging/index.js +323 -7
- package/dist/builtins/loki-logging/index.mjs +323 -7
- package/dist/builtins/loki-logging/loki-destination.d.ts +20 -3
- package/dist/builtins/loki-logging/loki-read.d.ts +51 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +290 -112
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +290 -112
- package/dist/builtins/native-metrics/node-metrics-history-recorder.d.ts +106 -35
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/record-entries/index.js +1 -1
- package/dist/builtins/record-entries/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- package/dist/builtins/storage-orchestrator/location-history-recorder.d.ts +76 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +9 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +144 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +144 -1
- package/dist/builtins/system-config/system-config.addon.js +3 -3
- package/dist/builtins/system-config/system-config.addon.mjs +3 -3
- package/dist/builtins/winston-logging/index.js +165 -3
- package/dist/builtins/winston-logging/index.mjs +165 -3
- package/dist/builtins/winston-logging/log-file-cursor.d.ts +12 -0
- package/dist/builtins/winston-logging/log-file-index-store.d.ts +11 -0
- package/dist/builtins/winston-logging/log-file-index.d.ts +77 -0
- package/dist/builtins/winston-logging/log-file-read-client.d.ts +44 -0
- package/dist/builtins/winston-logging/log-file-read-protocol.d.ts +41 -0
- package/dist/builtins/winston-logging/log-file-read-worker.d.ts +1 -0
- package/dist/builtins/winston-logging/log-file-reader.d.ts +51 -0
- package/dist/builtins/winston-logging/winston-destination.d.ts +20 -2
- package/dist/{composition-sources-Ds0Vr6eD.mjs → composition-sources-B_jeZDRJ.mjs} +1 -1
- package/dist/{composition-sources-gB4coDJR.js → composition-sources-qKyvMAvc.js} +1 -1
- package/dist/{constants-CavyKfHZ.js → constants-B_X9cWEK.js} +1 -1
- package/dist/{constants-i5oD_gLB.mjs → constants-cxKcZRMi.mjs} +1 -1
- package/dist/{device-ca-qz1Pmeu1.mjs → device-ca-BB6nHAL5.mjs} +1 -1
- package/dist/{device-ca-bpoEr0zG.js → device-ca-CVK3YcR_.js} +1 -1
- package/dist/{dist-BpqA2CT0.js → dist-C89nqNnn.js} +471 -47
- package/dist/{dist-BSF_C8W2.mjs → dist-Dhux90yB.mjs} +454 -48
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-CHaGDzyz.mjs → hub-mdns-ciao-B8olZqqd.mjs} +2 -2
- package/dist/{hub-mdns-ciao-CZlZRxxA.js → hub-mdns-ciao-DB1Y9agt.js} +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +135 -20
- package/dist/index.mjs +129 -21
- package/dist/kernel/config-schema.d.ts +5 -0
- package/dist/kernel/moleculer/accessory-prune-gate.d.ts +55 -0
- package/dist/log-file-read-protocol-B7M3X3bT.mjs +785 -0
- package/dist/log-file-read-protocol-D0WcLbIV.js +817 -0
- package/dist/log-file-read-worker.js +64 -0
- package/dist/log-file-read-worker.mjs +64 -0
- package/dist/log-read-filter-Bi066jwU.js +134 -0
- package/dist/log-read-filter-D7Ev3zm-.mjs +93 -0
- package/dist/logging/log-manager.d.ts +10 -6
- package/dist/logging/log-read-filter.d.ts +43 -0
- package/dist/{phase6-declare-gate-n-IZRklX.js → phase6-declare-gate-BO-_jLh_.js} +1 -1
- package/dist/{phase6-declare-gate-B4yfsf2n.mjs → phase6-declare-gate-x9ZCbQJP.mjs} +1 -1
- package/dist/{restore-archive-B4QG7XmX.js → restore-archive-4DOUR5Gu.js} +1 -1
- package/dist/{restore-archive-BGUM4FKf.mjs → restore-archive-DyOaPeAr.mjs} +1 -1
- package/dist/{wal-maintenance-AerF4XE6.mjs → wal-maintenance-BMiX6UIa.mjs} +0 -0
- package/dist/{wal-maintenance-CNEs_I94.js → wal-maintenance-DVN-DiVP.js} +0 -0
- package/package.json +1 -1
|
@@ -3,14 +3,132 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-C89nqNnn.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
|
+
const require_log_file_read_protocol = require("../../log-file-read-protocol-D0WcLbIV.js");
|
|
8
9
|
let node_path = require("node:path");
|
|
9
10
|
node_path = require_chunk.__toESM(node_path);
|
|
11
|
+
let node_module = require("node:module");
|
|
12
|
+
let node_worker_threads = require("node:worker_threads");
|
|
10
13
|
let winston = require("winston");
|
|
11
14
|
winston = require_chunk.__toESM(winston);
|
|
12
15
|
let winston_daily_rotate_file = require("winston-daily-rotate-file");
|
|
13
16
|
winston_daily_rotate_file = require_chunk.__toESM(winston_daily_rotate_file);
|
|
17
|
+
//#region src/builtins/winston-logging/log-file-read-client.ts
|
|
18
|
+
/**
|
|
19
|
+
* The parent side of the rotated-file reader thread (D898).
|
|
20
|
+
*
|
|
21
|
+
* The thread is spawned on the first read, not at boot: a hub nobody reads
|
|
22
|
+
* logs on pays nothing but the background index pass, which starts with the
|
|
23
|
+
* thread. A thread that dies fails its in-flight reads and is respawned on
|
|
24
|
+
* the next one.
|
|
25
|
+
*/
|
|
26
|
+
/** `dist/log-file-read-worker.js` at the package root, found through its own `package.json`. */
|
|
27
|
+
function defaultReaderWorkerPath() {
|
|
28
|
+
const require_ = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
29
|
+
const packageRoot = node_path.dirname(require_.resolve("@camstack/system/package.json"));
|
|
30
|
+
return node_path.join(packageRoot, "dist", "log-file-read-worker.js");
|
|
31
|
+
}
|
|
32
|
+
var LogFileReadClient = class {
|
|
33
|
+
logsDir;
|
|
34
|
+
backgroundPassMs;
|
|
35
|
+
spawn;
|
|
36
|
+
worker = null;
|
|
37
|
+
nextId = 1;
|
|
38
|
+
pending = /* @__PURE__ */ new Map();
|
|
39
|
+
local;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.logsDir = options.logsDir;
|
|
42
|
+
this.backgroundPassMs = options.backgroundPassMs ?? 6e4;
|
|
43
|
+
if (options.inProcess === true) {
|
|
44
|
+
this.spawn = null;
|
|
45
|
+
this.local = {
|
|
46
|
+
store: new require_log_file_read_protocol.LogFileIndexStore(options.logsDir),
|
|
47
|
+
cancels: new require_log_file_read_protocol.CancelBook()
|
|
48
|
+
};
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.local = null;
|
|
52
|
+
const workerPath = options.workerPath ?? defaultReaderWorkerPath();
|
|
53
|
+
const execArgv = options.workerExecArgv;
|
|
54
|
+
this.spawn = options.workerFactory ?? ((workerData) => new node_worker_threads.Worker(workerPath, {
|
|
55
|
+
workerData,
|
|
56
|
+
...execArgv !== void 0 ? { execArgv: [...execArgv] } : {}
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
async read(query) {
|
|
60
|
+
if (this.local !== null) return (await require_log_file_read_protocol.serveRead(this.logsDir, this.local.store, this.local.cancels, query)).result;
|
|
61
|
+
const worker = this.ensureWorker();
|
|
62
|
+
const id = this.nextId++;
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
this.pending.set(id, {
|
|
65
|
+
resolve,
|
|
66
|
+
reject
|
|
67
|
+
});
|
|
68
|
+
worker.postMessage({
|
|
69
|
+
type: "read",
|
|
70
|
+
id,
|
|
71
|
+
query
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Start the thread now, so its background pass indexes the existing files
|
|
77
|
+
* before anyone reads them. The hub calls this; an agent, whose files
|
|
78
|
+
* nobody reads, leaves the thread unstarted.
|
|
79
|
+
*/
|
|
80
|
+
warm() {
|
|
81
|
+
if (this.local === null) this.ensureWorker();
|
|
82
|
+
}
|
|
83
|
+
cancel(requestId) {
|
|
84
|
+
if (this.local !== null) {
|
|
85
|
+
this.local.cancels.cancel(requestId);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (this.worker === null) return false;
|
|
89
|
+
this.worker.postMessage({
|
|
90
|
+
type: "cancel",
|
|
91
|
+
requestId
|
|
92
|
+
});
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
stop() {
|
|
96
|
+
this.worker?.postMessage({ type: "stop" });
|
|
97
|
+
this.worker = null;
|
|
98
|
+
this.failAll(/* @__PURE__ */ new Error("log reader stopped"));
|
|
99
|
+
}
|
|
100
|
+
ensureWorker() {
|
|
101
|
+
if (this.worker !== null) return this.worker;
|
|
102
|
+
if (this.spawn === null) throw new Error("log reader has no worker factory");
|
|
103
|
+
const worker = this.spawn({
|
|
104
|
+
logsDir: this.logsDir,
|
|
105
|
+
backgroundPassMs: this.backgroundPassMs
|
|
106
|
+
});
|
|
107
|
+
worker.unref?.();
|
|
108
|
+
worker.on("message", (message) => {
|
|
109
|
+
if (!require_log_file_read_protocol.isFromReader(message)) return;
|
|
110
|
+
const waiter = this.pending.get(message.id);
|
|
111
|
+
if (waiter === void 0) return;
|
|
112
|
+
this.pending.delete(message.id);
|
|
113
|
+
if (message.type === "result") waiter.resolve(message.result);
|
|
114
|
+
else waiter.reject(new Error(message.message));
|
|
115
|
+
});
|
|
116
|
+
worker.on("error", (err) => {
|
|
117
|
+
this.failAll(err instanceof Error ? err : new Error(String(err)));
|
|
118
|
+
});
|
|
119
|
+
worker.on("exit", () => {
|
|
120
|
+
if (this.worker === worker) this.worker = null;
|
|
121
|
+
this.failAll(/* @__PURE__ */ new Error("log reader thread exited"));
|
|
122
|
+
});
|
|
123
|
+
this.worker = worker;
|
|
124
|
+
return worker;
|
|
125
|
+
}
|
|
126
|
+
failAll(error) {
|
|
127
|
+
for (const waiter of this.pending.values()) waiter.reject(error);
|
|
128
|
+
this.pending.clear();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
//#endregion
|
|
14
132
|
//#region src/builtins/winston-logging/winston-destination.ts
|
|
15
133
|
/**
|
|
16
134
|
* Rotated-file log destination. Console output goes through the shared
|
|
@@ -19,9 +137,24 @@ winston_daily_rotate_file = require_chunk.__toESM(winston_daily_rotate_file);
|
|
|
19
137
|
* structured JSON shape so log tooling can query tags/meta.
|
|
20
138
|
*/
|
|
21
139
|
var WinstonDestination = class {
|
|
140
|
+
readerOptions;
|
|
22
141
|
logger = null;
|
|
142
|
+
reader = null;
|
|
143
|
+
retentionDays = 30;
|
|
144
|
+
/**
|
|
145
|
+
* `readerOptions` reaches the history reader (D898) — a spec runs it in
|
|
146
|
+
* process or points the thread at the TypeScript source.
|
|
147
|
+
*/
|
|
148
|
+
constructor(readerOptions = {}) {
|
|
149
|
+
this.readerOptions = readerOptions;
|
|
150
|
+
}
|
|
23
151
|
async initialize(config) {
|
|
24
152
|
const { level = "info", retentionDays = 30, logsDir = node_path.join("camstack-data", "logs") } = config ?? {};
|
|
153
|
+
this.retentionDays = retentionDays;
|
|
154
|
+
this.reader = new LogFileReadClient({
|
|
155
|
+
...this.readerOptions,
|
|
156
|
+
logsDir
|
|
157
|
+
});
|
|
25
158
|
winston.format.printf((info) => {
|
|
26
159
|
const rendered = info._rendered;
|
|
27
160
|
return typeof rendered === "string" ? rendered : String(info.message);
|
|
@@ -47,6 +180,7 @@ var WinstonDestination = class {
|
|
|
47
180
|
const ts = entry.timestamp instanceof Date ? entry.timestamp.toISOString() : String(entry.timestamp);
|
|
48
181
|
const rendered = require_formatter.formatLogLine(entry, { colorize: true });
|
|
49
182
|
this.logger.log({
|
|
183
|
+
...entry.id !== void 0 ? { id: entry.id } : {},
|
|
50
184
|
level: entry.level,
|
|
51
185
|
message: entry.message,
|
|
52
186
|
_rendered: rendered,
|
|
@@ -56,10 +190,37 @@ var WinstonDestination = class {
|
|
|
56
190
|
...entry.meta ? { meta: entry.meta } : {}
|
|
57
191
|
});
|
|
58
192
|
}
|
|
59
|
-
|
|
60
|
-
|
|
193
|
+
/** The rotated files ARE the hub's history (D898): every filter is applied here. */
|
|
194
|
+
async describeRead() {
|
|
195
|
+
return {
|
|
196
|
+
readable: this.reader !== null,
|
|
197
|
+
reason: this.reader !== null ? null : "Not initialized",
|
|
198
|
+
filters: {
|
|
199
|
+
deviceId: true,
|
|
200
|
+
addonId: true,
|
|
201
|
+
nodeId: true,
|
|
202
|
+
tags: true,
|
|
203
|
+
level: true,
|
|
204
|
+
text: true,
|
|
205
|
+
timeRange: true
|
|
206
|
+
},
|
|
207
|
+
retentionDays: this.retentionDays
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** Index the existing files in the background now (the hub's history). */
|
|
211
|
+
startBackgroundIndexing() {
|
|
212
|
+
this.reader?.warm();
|
|
213
|
+
}
|
|
214
|
+
async read(query) {
|
|
215
|
+
if (this.reader === null) throw new Error("winston-logging: not initialized");
|
|
216
|
+
return this.reader.read(query);
|
|
217
|
+
}
|
|
218
|
+
async cancelRead(input) {
|
|
219
|
+
return { cancelled: this.reader?.cancel(input.requestId) ?? false };
|
|
61
220
|
}
|
|
62
221
|
async shutdown() {
|
|
222
|
+
this.reader?.stop();
|
|
223
|
+
this.reader = null;
|
|
63
224
|
if (!this.logger) return;
|
|
64
225
|
await new Promise((resolve) => {
|
|
65
226
|
this.logger.on("finish", resolve);
|
|
@@ -89,6 +250,7 @@ var WinstonLoggingAddon = class extends require_dist.BaseAddon {
|
|
|
89
250
|
...this.config,
|
|
90
251
|
logsDir
|
|
91
252
|
});
|
|
253
|
+
if (this.ctx.kernel.localNodeId === "hub") this.destination.startBackgroundIndexing();
|
|
92
254
|
this.ctx.logger.info("Winston logging initialized");
|
|
93
255
|
return [{
|
|
94
256
|
capability: require_dist.logDestinationCapability,
|
|
@@ -1,9 +1,127 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ni as BaseAddon, zn as logDestinationCapability } from "../../dist-Dhux90yB.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
|
+
import { a as LogFileIndexStore, i as serveRead, n as isFromReader, t as CancelBook } from "../../log-file-read-protocol-B7M3X3bT.mjs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
3
5
|
import * as path$1 from "node:path";
|
|
4
6
|
import path from "node:path";
|
|
7
|
+
import { Worker } from "node:worker_threads";
|
|
5
8
|
import * as winston from "winston";
|
|
6
9
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
10
|
+
//#region src/builtins/winston-logging/log-file-read-client.ts
|
|
11
|
+
/**
|
|
12
|
+
* The parent side of the rotated-file reader thread (D898).
|
|
13
|
+
*
|
|
14
|
+
* The thread is spawned on the first read, not at boot: a hub nobody reads
|
|
15
|
+
* logs on pays nothing but the background index pass, which starts with the
|
|
16
|
+
* thread. A thread that dies fails its in-flight reads and is respawned on
|
|
17
|
+
* the next one.
|
|
18
|
+
*/
|
|
19
|
+
/** `dist/log-file-read-worker.js` at the package root, found through its own `package.json`. */
|
|
20
|
+
function defaultReaderWorkerPath() {
|
|
21
|
+
const require_ = createRequire(import.meta.url);
|
|
22
|
+
const packageRoot = path$1.dirname(require_.resolve("@camstack/system/package.json"));
|
|
23
|
+
return path$1.join(packageRoot, "dist", "log-file-read-worker.js");
|
|
24
|
+
}
|
|
25
|
+
var LogFileReadClient = class {
|
|
26
|
+
logsDir;
|
|
27
|
+
backgroundPassMs;
|
|
28
|
+
spawn;
|
|
29
|
+
worker = null;
|
|
30
|
+
nextId = 1;
|
|
31
|
+
pending = /* @__PURE__ */ new Map();
|
|
32
|
+
local;
|
|
33
|
+
constructor(options) {
|
|
34
|
+
this.logsDir = options.logsDir;
|
|
35
|
+
this.backgroundPassMs = options.backgroundPassMs ?? 6e4;
|
|
36
|
+
if (options.inProcess === true) {
|
|
37
|
+
this.spawn = null;
|
|
38
|
+
this.local = {
|
|
39
|
+
store: new LogFileIndexStore(options.logsDir),
|
|
40
|
+
cancels: new CancelBook()
|
|
41
|
+
};
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.local = null;
|
|
45
|
+
const workerPath = options.workerPath ?? defaultReaderWorkerPath();
|
|
46
|
+
const execArgv = options.workerExecArgv;
|
|
47
|
+
this.spawn = options.workerFactory ?? ((workerData) => new Worker(workerPath, {
|
|
48
|
+
workerData,
|
|
49
|
+
...execArgv !== void 0 ? { execArgv: [...execArgv] } : {}
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
async read(query) {
|
|
53
|
+
if (this.local !== null) return (await serveRead(this.logsDir, this.local.store, this.local.cancels, query)).result;
|
|
54
|
+
const worker = this.ensureWorker();
|
|
55
|
+
const id = this.nextId++;
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
this.pending.set(id, {
|
|
58
|
+
resolve,
|
|
59
|
+
reject
|
|
60
|
+
});
|
|
61
|
+
worker.postMessage({
|
|
62
|
+
type: "read",
|
|
63
|
+
id,
|
|
64
|
+
query
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Start the thread now, so its background pass indexes the existing files
|
|
70
|
+
* before anyone reads them. The hub calls this; an agent, whose files
|
|
71
|
+
* nobody reads, leaves the thread unstarted.
|
|
72
|
+
*/
|
|
73
|
+
warm() {
|
|
74
|
+
if (this.local === null) this.ensureWorker();
|
|
75
|
+
}
|
|
76
|
+
cancel(requestId) {
|
|
77
|
+
if (this.local !== null) {
|
|
78
|
+
this.local.cancels.cancel(requestId);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (this.worker === null) return false;
|
|
82
|
+
this.worker.postMessage({
|
|
83
|
+
type: "cancel",
|
|
84
|
+
requestId
|
|
85
|
+
});
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
stop() {
|
|
89
|
+
this.worker?.postMessage({ type: "stop" });
|
|
90
|
+
this.worker = null;
|
|
91
|
+
this.failAll(/* @__PURE__ */ new Error("log reader stopped"));
|
|
92
|
+
}
|
|
93
|
+
ensureWorker() {
|
|
94
|
+
if (this.worker !== null) return this.worker;
|
|
95
|
+
if (this.spawn === null) throw new Error("log reader has no worker factory");
|
|
96
|
+
const worker = this.spawn({
|
|
97
|
+
logsDir: this.logsDir,
|
|
98
|
+
backgroundPassMs: this.backgroundPassMs
|
|
99
|
+
});
|
|
100
|
+
worker.unref?.();
|
|
101
|
+
worker.on("message", (message) => {
|
|
102
|
+
if (!isFromReader(message)) return;
|
|
103
|
+
const waiter = this.pending.get(message.id);
|
|
104
|
+
if (waiter === void 0) return;
|
|
105
|
+
this.pending.delete(message.id);
|
|
106
|
+
if (message.type === "result") waiter.resolve(message.result);
|
|
107
|
+
else waiter.reject(new Error(message.message));
|
|
108
|
+
});
|
|
109
|
+
worker.on("error", (err) => {
|
|
110
|
+
this.failAll(err instanceof Error ? err : new Error(String(err)));
|
|
111
|
+
});
|
|
112
|
+
worker.on("exit", () => {
|
|
113
|
+
if (this.worker === worker) this.worker = null;
|
|
114
|
+
this.failAll(/* @__PURE__ */ new Error("log reader thread exited"));
|
|
115
|
+
});
|
|
116
|
+
this.worker = worker;
|
|
117
|
+
return worker;
|
|
118
|
+
}
|
|
119
|
+
failAll(error) {
|
|
120
|
+
for (const waiter of this.pending.values()) waiter.reject(error);
|
|
121
|
+
this.pending.clear();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
//#endregion
|
|
7
125
|
//#region src/builtins/winston-logging/winston-destination.ts
|
|
8
126
|
/**
|
|
9
127
|
* Rotated-file log destination. Console output goes through the shared
|
|
@@ -12,9 +130,24 @@ import DailyRotateFile from "winston-daily-rotate-file";
|
|
|
12
130
|
* structured JSON shape so log tooling can query tags/meta.
|
|
13
131
|
*/
|
|
14
132
|
var WinstonDestination = class {
|
|
133
|
+
readerOptions;
|
|
15
134
|
logger = null;
|
|
135
|
+
reader = null;
|
|
136
|
+
retentionDays = 30;
|
|
137
|
+
/**
|
|
138
|
+
* `readerOptions` reaches the history reader (D898) — a spec runs it in
|
|
139
|
+
* process or points the thread at the TypeScript source.
|
|
140
|
+
*/
|
|
141
|
+
constructor(readerOptions = {}) {
|
|
142
|
+
this.readerOptions = readerOptions;
|
|
143
|
+
}
|
|
16
144
|
async initialize(config) {
|
|
17
145
|
const { level = "info", retentionDays = 30, logsDir = path$1.join("camstack-data", "logs") } = config ?? {};
|
|
146
|
+
this.retentionDays = retentionDays;
|
|
147
|
+
this.reader = new LogFileReadClient({
|
|
148
|
+
...this.readerOptions,
|
|
149
|
+
logsDir
|
|
150
|
+
});
|
|
18
151
|
winston.format.printf((info) => {
|
|
19
152
|
const rendered = info._rendered;
|
|
20
153
|
return typeof rendered === "string" ? rendered : String(info.message);
|
|
@@ -40,6 +173,7 @@ var WinstonDestination = class {
|
|
|
40
173
|
const ts = entry.timestamp instanceof Date ? entry.timestamp.toISOString() : String(entry.timestamp);
|
|
41
174
|
const rendered = formatLogLine(entry, { colorize: true });
|
|
42
175
|
this.logger.log({
|
|
176
|
+
...entry.id !== void 0 ? { id: entry.id } : {},
|
|
43
177
|
level: entry.level,
|
|
44
178
|
message: entry.message,
|
|
45
179
|
_rendered: rendered,
|
|
@@ -49,10 +183,37 @@ var WinstonDestination = class {
|
|
|
49
183
|
...entry.meta ? { meta: entry.meta } : {}
|
|
50
184
|
});
|
|
51
185
|
}
|
|
52
|
-
|
|
53
|
-
|
|
186
|
+
/** The rotated files ARE the hub's history (D898): every filter is applied here. */
|
|
187
|
+
async describeRead() {
|
|
188
|
+
return {
|
|
189
|
+
readable: this.reader !== null,
|
|
190
|
+
reason: this.reader !== null ? null : "Not initialized",
|
|
191
|
+
filters: {
|
|
192
|
+
deviceId: true,
|
|
193
|
+
addonId: true,
|
|
194
|
+
nodeId: true,
|
|
195
|
+
tags: true,
|
|
196
|
+
level: true,
|
|
197
|
+
text: true,
|
|
198
|
+
timeRange: true
|
|
199
|
+
},
|
|
200
|
+
retentionDays: this.retentionDays
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/** Index the existing files in the background now (the hub's history). */
|
|
204
|
+
startBackgroundIndexing() {
|
|
205
|
+
this.reader?.warm();
|
|
206
|
+
}
|
|
207
|
+
async read(query) {
|
|
208
|
+
if (this.reader === null) throw new Error("winston-logging: not initialized");
|
|
209
|
+
return this.reader.read(query);
|
|
210
|
+
}
|
|
211
|
+
async cancelRead(input) {
|
|
212
|
+
return { cancelled: this.reader?.cancel(input.requestId) ?? false };
|
|
54
213
|
}
|
|
55
214
|
async shutdown() {
|
|
215
|
+
this.reader?.stop();
|
|
216
|
+
this.reader = null;
|
|
56
217
|
if (!this.logger) return;
|
|
57
218
|
await new Promise((resolve) => {
|
|
58
219
|
this.logger.on("finish", resolve);
|
|
@@ -82,6 +243,7 @@ var WinstonLoggingAddon = class extends BaseAddon {
|
|
|
82
243
|
...this.config,
|
|
83
244
|
logsDir
|
|
84
245
|
});
|
|
246
|
+
if (this.ctx.kernel.localNodeId === "hub") this.destination.startBackgroundIndexing();
|
|
85
247
|
this.ctx.logger.info("Winston logging initialized");
|
|
86
248
|
return [{
|
|
87
249
|
capability: logDestinationCapability,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** `camstack-YYYY-MM-DD.log`, as `winston-daily-rotate-file` names them. */
|
|
2
|
+
export declare const LOG_FILE_NAME_RE: RegExp;
|
|
3
|
+
/** A position in one daily file. */
|
|
4
|
+
export interface FileCursor {
|
|
5
|
+
/** The daily file's name (never a path). */
|
|
6
|
+
readonly f: string;
|
|
7
|
+
/** A line-start byte offset in that file. */
|
|
8
|
+
readonly o: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function encodeFileCursor(cursor: FileCursor): string;
|
|
11
|
+
/** Null for anything this provider did not mint. */
|
|
12
|
+
export declare function decodeFileCursor(raw: string): FileCursor | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FileIndex } from "./log-file-index.js";
|
|
2
|
+
export declare class LogFileIndexStore {
|
|
3
|
+
private readonly logsDir;
|
|
4
|
+
private readonly files;
|
|
5
|
+
constructor(logsDir: string);
|
|
6
|
+
indexOf(fileName: string, opts?: {
|
|
7
|
+
readonly wait?: boolean;
|
|
8
|
+
}): Promise<FileIndex | null>;
|
|
9
|
+
/** Index everything, newest first; drop orphaned sidecars. */
|
|
10
|
+
backgroundPass(shouldStop: () => boolean): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export declare const INDEX_VERSION = 1;
|
|
2
|
+
export declare const BLOCK_BYTES: number;
|
|
3
|
+
/** Above this many distinct values a block's set becomes `null` (anything). */
|
|
4
|
+
export declare const MAX_SET = 256;
|
|
5
|
+
/** One completed block of the sidecar. */
|
|
6
|
+
export interface IndexBlock {
|
|
7
|
+
/** Byte range [s, e), e on a line boundary. */
|
|
8
|
+
readonly s: number;
|
|
9
|
+
readonly e: number;
|
|
10
|
+
readonly n: number;
|
|
11
|
+
/** Epoch ms range; null when a line's timestamp could not be read. */
|
|
12
|
+
readonly t0: number | null;
|
|
13
|
+
readonly t1: number | null;
|
|
14
|
+
readonly d: number[] | null;
|
|
15
|
+
readonly a: string[] | null;
|
|
16
|
+
readonly o: string[] | null;
|
|
17
|
+
/** Highest level rank in the block. */
|
|
18
|
+
readonly l: number;
|
|
19
|
+
}
|
|
20
|
+
/** What one raw line contributes to its block. */
|
|
21
|
+
export interface LineFacts {
|
|
22
|
+
readonly ts: number | null;
|
|
23
|
+
readonly level: number;
|
|
24
|
+
readonly deviceIds: readonly number[];
|
|
25
|
+
readonly addonIds: readonly string[];
|
|
26
|
+
readonly nodes: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
/** Facts off the raw text — no JSON.parse. */
|
|
29
|
+
export declare function extractLineFacts(line: string): LineFacts;
|
|
30
|
+
/** Accumulates lines into one block. */
|
|
31
|
+
export declare class BlockBuilder {
|
|
32
|
+
readonly start: number;
|
|
33
|
+
private n;
|
|
34
|
+
private t0;
|
|
35
|
+
private t1;
|
|
36
|
+
private tsUnknown;
|
|
37
|
+
private readonly d;
|
|
38
|
+
private readonly a;
|
|
39
|
+
private readonly o;
|
|
40
|
+
private l;
|
|
41
|
+
constructor(start: number);
|
|
42
|
+
add(facts: LineFacts): void;
|
|
43
|
+
/** A line whose facts could not be read: the block may hold anything. */
|
|
44
|
+
addUnknown(): void;
|
|
45
|
+
get lines(): number;
|
|
46
|
+
finish(end: number): IndexBlock;
|
|
47
|
+
}
|
|
48
|
+
/** In-memory index state of one file. */
|
|
49
|
+
export interface FileIndex {
|
|
50
|
+
readonly ino: number;
|
|
51
|
+
/** Completed blocks, ascending, tiling [0, indexedEnd). */
|
|
52
|
+
readonly blocks: IndexBlock[];
|
|
53
|
+
}
|
|
54
|
+
export declare function indexEnd(index: FileIndex): number;
|
|
55
|
+
export declare function indexPathOf(logPath: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Load `<file>.idx`. Returns an empty index (to be rebuilt) when the sidecar
|
|
58
|
+
* is missing, belongs to another inode, or is not a contiguous tiling; keeps
|
|
59
|
+
* the valid prefix when only its tail is torn.
|
|
60
|
+
*/
|
|
61
|
+
export declare function loadFileIndex(logPath: string, ino: number, size: number): Promise<{
|
|
62
|
+
readonly index: FileIndex;
|
|
63
|
+
/** True when the sidecar must be rewritten from `index` before appending. */
|
|
64
|
+
readonly rewrite: boolean;
|
|
65
|
+
}>;
|
|
66
|
+
/** Something that yields between chunks so a reader on the same thread can run. */
|
|
67
|
+
export type Yield = () => Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Extend `index` over whatever complete lines were appended to the file since
|
|
70
|
+
* its last completed block, persisting each block as it completes. Returns
|
|
71
|
+
* the number of bytes read.
|
|
72
|
+
*/
|
|
73
|
+
export declare function catchUpFileIndex(logPath: string, index: FileIndex, opts: {
|
|
74
|
+
readonly rewrite: boolean;
|
|
75
|
+
readonly yieldFn?: Yield;
|
|
76
|
+
readonly shouldStop?: () => boolean;
|
|
77
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LogReadQuery, LogReadResult } from "@camstack/types";
|
|
2
|
+
import { type ReadWorkerData, type ToReader } from "./log-file-read-protocol.js";
|
|
3
|
+
/** The structural surface of a `Worker` this client uses. */
|
|
4
|
+
export interface ReaderWorkerHandle {
|
|
5
|
+
on(event: "message", listener: (message: unknown) => void): unknown;
|
|
6
|
+
on(event: "error", listener: (err: unknown) => void): unknown;
|
|
7
|
+
on(event: "exit", listener: (code: number) => void): unknown;
|
|
8
|
+
postMessage(message: ToReader): void;
|
|
9
|
+
unref?(): void;
|
|
10
|
+
}
|
|
11
|
+
export type ReaderWorkerFactory = (data: ReadWorkerData) => ReaderWorkerHandle;
|
|
12
|
+
/** `dist/log-file-read-worker.js` at the package root, found through its own `package.json`. */
|
|
13
|
+
export declare function defaultReaderWorkerPath(): string;
|
|
14
|
+
export interface LogFileReadClientOptions {
|
|
15
|
+
readonly logsDir: string;
|
|
16
|
+
/** Background index pass period. Default 60 s; 0 disables (specs). */
|
|
17
|
+
readonly backgroundPassMs?: number;
|
|
18
|
+
/** Run in the calling thread — specs only. */
|
|
19
|
+
readonly inProcess?: boolean;
|
|
20
|
+
readonly workerPath?: string;
|
|
21
|
+
readonly workerExecArgv?: readonly string[];
|
|
22
|
+
readonly workerFactory?: ReaderWorkerFactory;
|
|
23
|
+
}
|
|
24
|
+
export declare class LogFileReadClient {
|
|
25
|
+
private readonly logsDir;
|
|
26
|
+
private readonly backgroundPassMs;
|
|
27
|
+
private readonly spawn;
|
|
28
|
+
private worker;
|
|
29
|
+
private nextId;
|
|
30
|
+
private readonly pending;
|
|
31
|
+
private readonly local;
|
|
32
|
+
constructor(options: LogFileReadClientOptions);
|
|
33
|
+
read(query: LogReadQuery): Promise<LogReadResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Start the thread now, so its background pass indexes the existing files
|
|
36
|
+
* before anyone reads them. The hub calls this; an agent, whose files
|
|
37
|
+
* nobody reads, leaves the thread unstarted.
|
|
38
|
+
*/
|
|
39
|
+
warm(): void;
|
|
40
|
+
cancel(requestId: string): boolean;
|
|
41
|
+
stop(): void;
|
|
42
|
+
private ensureWorker;
|
|
43
|
+
private failAll;
|
|
44
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages between `WinstonDestination` and its reader thread (D898), and the
|
|
3
|
+
* one function both the thread and the in-process mode run.
|
|
4
|
+
*/
|
|
5
|
+
import type { LogReadQuery, LogReadResult } from "@camstack/types";
|
|
6
|
+
import type { LogFileIndexStore } from "./log-file-index-store.js";
|
|
7
|
+
import { type FileReadOutcome } from "./log-file-reader.js";
|
|
8
|
+
export interface ReadWorkerData {
|
|
9
|
+
readonly logsDir: string;
|
|
10
|
+
/** Period of the background index pass; 0 disables it (specs). */
|
|
11
|
+
readonly backgroundPassMs: number;
|
|
12
|
+
}
|
|
13
|
+
export type ToReader = {
|
|
14
|
+
readonly type: "read";
|
|
15
|
+
readonly id: number;
|
|
16
|
+
readonly query: LogReadQuery;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "cancel";
|
|
19
|
+
readonly requestId: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: "stop";
|
|
22
|
+
};
|
|
23
|
+
export type FromReader = {
|
|
24
|
+
readonly type: "result";
|
|
25
|
+
readonly id: number;
|
|
26
|
+
readonly result: LogReadResult;
|
|
27
|
+
} | {
|
|
28
|
+
readonly type: "error";
|
|
29
|
+
readonly id: number;
|
|
30
|
+
readonly message: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function isFromReader(value: unknown): value is FromReader;
|
|
33
|
+
export declare function isToReader(value: unknown): value is ToReader;
|
|
34
|
+
/** Requests cancelled before or while they run, with when, so the set cannot grow. */
|
|
35
|
+
export declare class CancelBook {
|
|
36
|
+
private readonly at;
|
|
37
|
+
cancel(requestId: string, now?: number): void;
|
|
38
|
+
isCancelled(requestId: string | undefined): boolean;
|
|
39
|
+
settle(requestId: string | undefined): void;
|
|
40
|
+
}
|
|
41
|
+
export declare function serveRead(logsDir: string, store: LogFileIndexStore, cancels: CancelBook, query: LogReadQuery): Promise<FileReadOutcome>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { LogReadEntry, LogReadQuery, LogReadResult } from "@camstack/types";
|
|
2
|
+
import { type CompiledLogFilter, type ReadableLine } from "../../logging/log-read-filter.js";
|
|
3
|
+
import { type FileIndex, type IndexBlock } from "./log-file-index.js";
|
|
4
|
+
export declare const DEFAULT_READ_LIMIT = 200;
|
|
5
|
+
export declare const DEFAULT_BUDGET_MS = 1e3;
|
|
6
|
+
export declare const MAX_BUDGET_MS = 5e3;
|
|
7
|
+
export interface FileReadStats {
|
|
8
|
+
/** Blocks the index ruled out — never read. */
|
|
9
|
+
blocksSkipped: number;
|
|
10
|
+
/** Blocks (or unindexed regions) actually read. */
|
|
11
|
+
regionsRead: number;
|
|
12
|
+
/** Of those, INDEXED blocks — the ones the index could have skipped. */
|
|
13
|
+
blocksRead: number;
|
|
14
|
+
/** Lines handed to JSON.parse. */
|
|
15
|
+
linesParsed: number;
|
|
16
|
+
}
|
|
17
|
+
export interface FileReadOutcome {
|
|
18
|
+
readonly result: LogReadResult;
|
|
19
|
+
readonly stats: FileReadStats;
|
|
20
|
+
}
|
|
21
|
+
export interface FileReadContext {
|
|
22
|
+
readonly logsDir: string;
|
|
23
|
+
/** The file's index as far as it reaches (may be empty — then nothing is skipped). */
|
|
24
|
+
readonly indexOf: (fileName: string) => Promise<FileIndex | null>;
|
|
25
|
+
readonly isCancelled: () => boolean;
|
|
26
|
+
readonly now?: () => number;
|
|
27
|
+
}
|
|
28
|
+
/** What a reader can rule out from a block alone. */
|
|
29
|
+
export declare function blockMayMatch(block: IndexBlock, filter: CompiledLogFilter): boolean;
|
|
30
|
+
interface ParsedRow {
|
|
31
|
+
readonly entry: LogReadEntry;
|
|
32
|
+
readonly line: ReadableLine;
|
|
33
|
+
}
|
|
34
|
+
/** One JSON row → a read entry, or null for a line that is not one of ours. */
|
|
35
|
+
export declare function parseLogRow(text: string): ParsedRow | null;
|
|
36
|
+
/** Daily files, newest first. */
|
|
37
|
+
export declare function listLogFiles(logsDir: string): Promise<string[]>;
|
|
38
|
+
/**
|
|
39
|
+
* One backward slice. `entries` newest first; `nextBefore` resumes exactly
|
|
40
|
+
* where this slice stopped.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readLogFilesBackward(ctx: FileReadContext, query: LogReadQuery): Promise<FileReadOutcome>;
|
|
43
|
+
/**
|
|
44
|
+
* One forward slice: lines strictly after `after`, scanned oldest first and
|
|
45
|
+
* returned newest first like every page. `nextAfter` is where the next poll
|
|
46
|
+
* starts; `done` means the scan reached the end of the newest file.
|
|
47
|
+
*/
|
|
48
|
+
export declare function readLogFilesForward(ctx: FileReadContext, query: LogReadQuery & {
|
|
49
|
+
readonly after: string;
|
|
50
|
+
}): Promise<FileReadOutcome>;
|
|
51
|
+
export {};
|