@camstack/system 1.2.456 → 1.2.458
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 +259 -37
- package/dist/addon-runner.mjs +289 -67
- package/dist/{agent-https-fm4AR8XL.mjs → agent-https-CQA3W-3e.mjs} +2 -2
- package/dist/{agent-https-yjxdfJDA.js → agent-https-DOELEug8.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.d.ts +13 -0
- package/dist/builtins/device-history/device-history.addon.js +736 -315
- package/dist/builtins/device-history/device-history.addon.mjs +736 -315
- package/dist/builtins/device-history/ingest/producer-ingest.d.ts +11 -1
- 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/coral-accelerators.d.ts +26 -9
- package/dist/builtins/platform-probe/index.js +11 -68
- package/dist/builtins/platform-probe/index.mjs +11 -68
- 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-BVguN7VV.mjs} +1 -1
- package/dist/{composition-sources-gB4coDJR.js → composition-sources-DURQNDJ4.js} +1 -1
- package/dist/{constants-i5oD_gLB.mjs → constants-FGjnsE2x.mjs} +1 -1
- package/dist/{constants-CavyKfHZ.js → constants-Njka8Cv6.js} +1 -1
- package/dist/coral-accelerators-Bi_c3Rft.js +175 -0
- package/dist/coral-accelerators-DcnZvctM.mjs +162 -0
- package/dist/{device-ca-bpoEr0zG.js → device-ca-BrVK_5s3.js} +1 -1
- package/dist/{device-ca-qz1Pmeu1.mjs → device-ca-lEaAQ9Xi.mjs} +1 -1
- package/dist/{dist-BpqA2CT0.js → dist-BZQjLPiz.js} +526 -57
- package/dist/{dist-BSF_C8W2.mjs → dist-B_GRPhnY.mjs} +509 -58
- 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-Bg2HfTwP.mjs} +2 -2
- package/dist/{hub-mdns-ciao-CZlZRxxA.js → hub-mdns-ciao-nce3TXs_.js} +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +140 -48
- package/dist/index.mjs +134 -49
- package/dist/kernel/config-schema.d.ts +5 -0
- package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +7 -3
- 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-Dag1_WR2.js} +1 -1
- package/dist/{phase6-declare-gate-B4yfsf2n.mjs → phase6-declare-gate-Lb-mrVvz.mjs} +1 -1
- package/dist/{restore-archive-BGUM4FKf.mjs → restore-archive-C7kLDI_0.mjs} +1 -1
- package/dist/{restore-archive-B4QG7XmX.js → restore-archive-FyfZ8YMC.js} +1 -1
- package/dist/{wal-maintenance-CNEs_I94.js → wal-maintenance-DfSIOj24.js} +0 -0
- package/dist/{wal-maintenance-AerF4XE6.mjs → wal-maintenance-Duejh7Lx.mjs} +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
+
const require_log_read_filter = require("./log-read-filter-Bi066jwU.js");
|
|
3
|
+
let zod = require("zod");
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
node_path = require_chunk.__toESM(node_path);
|
|
8
|
+
//#region src/builtins/winston-logging/log-file-cursor.ts
|
|
9
|
+
/**
|
|
10
|
+
* The rotated-file cursor (D898): a byte position in one daily file.
|
|
11
|
+
*
|
|
12
|
+
* Files are append-only, so a position never moves while the file grows —
|
|
13
|
+
* that is the whole reason a cursor can be a byte offset. It is opaque to
|
|
14
|
+
* every caller (`w1.` + base64url JSON) and validated on the way back in: a
|
|
15
|
+
* cursor is client input, and its file name reaches `path.join`.
|
|
16
|
+
*/
|
|
17
|
+
/** `camstack-YYYY-MM-DD.log`, as `winston-daily-rotate-file` names them. */
|
|
18
|
+
var LOG_FILE_NAME_RE = /^camstack-\d{4}-\d{2}-\d{2}\.log$/;
|
|
19
|
+
var CURSOR_PREFIX = "w1.";
|
|
20
|
+
var FileCursorSchema = zod.z.object({
|
|
21
|
+
/** The daily file's name (never a path). */
|
|
22
|
+
f: zod.z.string().regex(LOG_FILE_NAME_RE),
|
|
23
|
+
/** A line-start byte offset in that file. */
|
|
24
|
+
o: zod.z.number().int().nonnegative()
|
|
25
|
+
});
|
|
26
|
+
function encodeFileCursor(cursor) {
|
|
27
|
+
return `${CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString("base64url")}`;
|
|
28
|
+
}
|
|
29
|
+
/** Null for anything this provider did not mint. */
|
|
30
|
+
function decodeFileCursor(raw) {
|
|
31
|
+
if (!raw.startsWith(CURSOR_PREFIX)) return null;
|
|
32
|
+
try {
|
|
33
|
+
const json = JSON.parse(Buffer.from(raw.slice(3), "base64url").toString("utf8"));
|
|
34
|
+
const parsed = FileCursorSchema.safeParse(json);
|
|
35
|
+
return parsed.success ? parsed.data : null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
var BLOCK_BYTES = 1024 * 1024;
|
|
41
|
+
/** Chunk size for reading the file while indexing. */
|
|
42
|
+
var READ_CHUNK = 4 * 1024 * 1024;
|
|
43
|
+
var LEVEL_RANK = {
|
|
44
|
+
debug: 0,
|
|
45
|
+
info: 1,
|
|
46
|
+
warn: 2,
|
|
47
|
+
error: 3
|
|
48
|
+
};
|
|
49
|
+
var IndexHeaderSchema = zod.z.object({
|
|
50
|
+
v: zod.z.literal(1),
|
|
51
|
+
ino: zod.z.number(),
|
|
52
|
+
block: zod.z.number().int().positive()
|
|
53
|
+
});
|
|
54
|
+
var IndexBlockSchema = zod.z.object({
|
|
55
|
+
/** Byte range [s, e), e on a line boundary. */
|
|
56
|
+
s: zod.z.number().int().nonnegative(),
|
|
57
|
+
e: zod.z.number().int().positive(),
|
|
58
|
+
n: zod.z.number().int().nonnegative(),
|
|
59
|
+
/** Epoch ms range; null when a line's timestamp could not be read. */
|
|
60
|
+
t0: zod.z.number().nullable(),
|
|
61
|
+
t1: zod.z.number().nullable(),
|
|
62
|
+
d: zod.z.array(zod.z.number()).nullable(),
|
|
63
|
+
a: zod.z.array(zod.z.string()).nullable(),
|
|
64
|
+
o: zod.z.array(zod.z.string()).nullable(),
|
|
65
|
+
/** Highest level rank in the block. */
|
|
66
|
+
l: zod.z.number().int().min(0).max(3)
|
|
67
|
+
});
|
|
68
|
+
var RE_LEVEL = /"level":"(debug|info|warn|error)"/;
|
|
69
|
+
var RE_TS = /"timestamp":"([^"\\]{10,40})"\}$/;
|
|
70
|
+
var RE_DEVICE = /"deviceId":(\d{1,15})(?=[,}])/g;
|
|
71
|
+
var RE_ADDON = /"addonId":"([^"\\]{1,200})"/g;
|
|
72
|
+
var RE_AGENT = /"agentId":"([^"\\]{1,200})"/g;
|
|
73
|
+
var RE_NODE = /"nodeId":"([^"\\/]{1,200})/g;
|
|
74
|
+
function bare(addonId) {
|
|
75
|
+
let out = addonId.split("@")[0] ?? addonId;
|
|
76
|
+
const colon = out.indexOf("::");
|
|
77
|
+
if (colon >= 0) out = out.slice(0, colon);
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
/** Facts off the raw text — no JSON.parse. */
|
|
81
|
+
function extractLineFacts(line) {
|
|
82
|
+
const level = RE_LEVEL.exec(line);
|
|
83
|
+
const ts = RE_TS.exec(line);
|
|
84
|
+
const parsedTs = ts?.[1] !== void 0 ? Date.parse(ts[1]) : NaN;
|
|
85
|
+
const deviceIds = [];
|
|
86
|
+
for (const m of line.matchAll(RE_DEVICE)) deviceIds.push(Number(m[1]));
|
|
87
|
+
const addonIds = [];
|
|
88
|
+
for (const m of line.matchAll(RE_ADDON)) if (m[1] !== void 0) addonIds.push(bare(m[1]));
|
|
89
|
+
const nodes = [];
|
|
90
|
+
for (const m of line.matchAll(RE_AGENT)) if (m[1] !== void 0) nodes.push(m[1]);
|
|
91
|
+
for (const m of line.matchAll(RE_NODE)) if (m[1] !== void 0) nodes.push(m[1]);
|
|
92
|
+
return {
|
|
93
|
+
ts: Number.isFinite(parsedTs) ? parsedTs : null,
|
|
94
|
+
level: level?.[1] !== void 0 ? LEVEL_RANK[level[1]] ?? 3 : 3,
|
|
95
|
+
deviceIds,
|
|
96
|
+
addonIds,
|
|
97
|
+
nodes
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
var GrowingSet = class {
|
|
101
|
+
values = /* @__PURE__ */ new Set();
|
|
102
|
+
add(value) {
|
|
103
|
+
if (this.values === null) return;
|
|
104
|
+
this.values.add(value);
|
|
105
|
+
if (this.values.size > 256) this.values = null;
|
|
106
|
+
}
|
|
107
|
+
poison() {
|
|
108
|
+
this.values = null;
|
|
109
|
+
}
|
|
110
|
+
toArray() {
|
|
111
|
+
return this.values === null ? null : [...this.values];
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/** Accumulates lines into one block. */
|
|
115
|
+
var BlockBuilder = class {
|
|
116
|
+
start;
|
|
117
|
+
n = 0;
|
|
118
|
+
t0 = Number.POSITIVE_INFINITY;
|
|
119
|
+
t1 = Number.NEGATIVE_INFINITY;
|
|
120
|
+
tsUnknown = false;
|
|
121
|
+
d = new GrowingSet();
|
|
122
|
+
a = new GrowingSet();
|
|
123
|
+
o = new GrowingSet();
|
|
124
|
+
l = 0;
|
|
125
|
+
constructor(start) {
|
|
126
|
+
this.start = start;
|
|
127
|
+
}
|
|
128
|
+
add(facts) {
|
|
129
|
+
this.n += 1;
|
|
130
|
+
if (facts.ts === null) this.tsUnknown = true;
|
|
131
|
+
else {
|
|
132
|
+
if (this.t0 !== null && facts.ts < this.t0) this.t0 = facts.ts;
|
|
133
|
+
if (this.t1 !== null && facts.ts > this.t1) this.t1 = facts.ts;
|
|
134
|
+
}
|
|
135
|
+
for (const id of facts.deviceIds) this.d.add(id);
|
|
136
|
+
for (const id of facts.addonIds) this.a.add(id);
|
|
137
|
+
for (const id of facts.nodes) this.o.add(id);
|
|
138
|
+
if (facts.level > this.l) this.l = facts.level;
|
|
139
|
+
}
|
|
140
|
+
/** A line whose facts could not be read: the block may hold anything. */
|
|
141
|
+
addUnknown() {
|
|
142
|
+
this.n += 1;
|
|
143
|
+
this.tsUnknown = true;
|
|
144
|
+
this.d.poison();
|
|
145
|
+
this.a.poison();
|
|
146
|
+
this.o.poison();
|
|
147
|
+
this.l = 3;
|
|
148
|
+
}
|
|
149
|
+
get lines() {
|
|
150
|
+
return this.n;
|
|
151
|
+
}
|
|
152
|
+
finish(end) {
|
|
153
|
+
const known = !this.tsUnknown && this.n > 0;
|
|
154
|
+
return {
|
|
155
|
+
s: this.start,
|
|
156
|
+
e: end,
|
|
157
|
+
n: this.n,
|
|
158
|
+
t0: known ? this.t0 : null,
|
|
159
|
+
t1: known ? this.t1 : null,
|
|
160
|
+
d: this.d.toArray(),
|
|
161
|
+
a: this.a.toArray(),
|
|
162
|
+
o: this.o.toArray(),
|
|
163
|
+
l: this.l
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function indexEnd(index) {
|
|
168
|
+
return index.blocks.at(-1)?.e ?? 0;
|
|
169
|
+
}
|
|
170
|
+
function indexPathOf(logPath) {
|
|
171
|
+
return `${logPath}.idx`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Load `<file>.idx`. Returns an empty index (to be rebuilt) when the sidecar
|
|
175
|
+
* is missing, belongs to another inode, or is not a contiguous tiling; keeps
|
|
176
|
+
* the valid prefix when only its tail is torn.
|
|
177
|
+
*/
|
|
178
|
+
async function loadFileIndex(logPath, ino, size) {
|
|
179
|
+
let raw;
|
|
180
|
+
try {
|
|
181
|
+
raw = await node_fs_promises.readFile(indexPathOf(logPath), "utf8");
|
|
182
|
+
} catch {
|
|
183
|
+
return {
|
|
184
|
+
index: {
|
|
185
|
+
ino,
|
|
186
|
+
blocks: []
|
|
187
|
+
},
|
|
188
|
+
rewrite: true
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const lines = raw.split("\n");
|
|
192
|
+
const header = safeJson(lines[0] ?? "");
|
|
193
|
+
const parsedHeader = IndexHeaderSchema.safeParse(header);
|
|
194
|
+
if (!parsedHeader.success || parsedHeader.data.ino !== ino) return {
|
|
195
|
+
index: {
|
|
196
|
+
ino,
|
|
197
|
+
blocks: []
|
|
198
|
+
},
|
|
199
|
+
rewrite: true
|
|
200
|
+
};
|
|
201
|
+
const blocks = [];
|
|
202
|
+
let expected = 0;
|
|
203
|
+
let torn = false;
|
|
204
|
+
for (const line of lines.slice(1)) {
|
|
205
|
+
if (line.length === 0) continue;
|
|
206
|
+
const block = IndexBlockSchema.safeParse(safeJson(line));
|
|
207
|
+
if (!block.success || block.data.s !== expected || block.data.e <= block.data.s || block.data.e > size) {
|
|
208
|
+
torn = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
blocks.push(block.data);
|
|
212
|
+
expected = block.data.e;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
index: {
|
|
216
|
+
ino,
|
|
217
|
+
blocks
|
|
218
|
+
},
|
|
219
|
+
rewrite: torn
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function safeJson(text) {
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(text);
|
|
225
|
+
} catch {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function rewriteSidecar(logPath, index) {
|
|
230
|
+
const header = JSON.stringify({
|
|
231
|
+
v: 1,
|
|
232
|
+
ino: index.ino,
|
|
233
|
+
block: BLOCK_BYTES
|
|
234
|
+
});
|
|
235
|
+
const body = index.blocks.map((b) => JSON.stringify(b)).join("\n");
|
|
236
|
+
const tmp = `${indexPathOf(logPath)}.tmp`;
|
|
237
|
+
await node_fs_promises.writeFile(tmp, body.length > 0 ? `${header}\n${body}\n` : `${header}\n`);
|
|
238
|
+
await node_fs_promises.rename(tmp, indexPathOf(logPath));
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Extend `index` over whatever complete lines were appended to the file since
|
|
242
|
+
* its last completed block, persisting each block as it completes. Returns
|
|
243
|
+
* the number of bytes read.
|
|
244
|
+
*/
|
|
245
|
+
async function catchUpFileIndex(logPath, index, opts) {
|
|
246
|
+
if (opts.rewrite) await rewriteSidecar(logPath, index);
|
|
247
|
+
const handle = await node_fs_promises.open(logPath, "r");
|
|
248
|
+
try {
|
|
249
|
+
const size = (await handle.stat()).size;
|
|
250
|
+
let pos = indexEnd(index);
|
|
251
|
+
let builder = new BlockBuilder(pos);
|
|
252
|
+
let readBytes = 0;
|
|
253
|
+
const buf = Buffer.allocUnsafe(READ_CHUNK);
|
|
254
|
+
while (pos < size) {
|
|
255
|
+
if (opts.shouldStop?.() === true) break;
|
|
256
|
+
const { bytesRead } = await handle.read(buf, 0, Math.min(READ_CHUNK, size - pos), pos);
|
|
257
|
+
if (bytesRead === 0) break;
|
|
258
|
+
readBytes += bytesRead;
|
|
259
|
+
const completed = [];
|
|
260
|
+
let lineStart = 0;
|
|
261
|
+
for (;;) {
|
|
262
|
+
const nl = buf.indexOf(10, lineStart);
|
|
263
|
+
if (nl === -1 || nl >= bytesRead) break;
|
|
264
|
+
builder.add(extractLineFacts(buf.toString("utf8", lineStart, nl)));
|
|
265
|
+
lineStart = nl + 1;
|
|
266
|
+
const absolute = pos + lineStart;
|
|
267
|
+
if (absolute - builder.start >= 1048576) {
|
|
268
|
+
const block = builder.finish(absolute);
|
|
269
|
+
index.blocks.push(block);
|
|
270
|
+
completed.push(block);
|
|
271
|
+
builder = new BlockBuilder(absolute);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (completed.length > 0) await node_fs_promises.appendFile(indexPathOf(logPath), `${completed.map((b) => JSON.stringify(b)).join("\n")}\n`);
|
|
275
|
+
if (lineStart === 0) {
|
|
276
|
+
if (bytesRead < READ_CHUNK) break;
|
|
277
|
+
builder.addUnknown();
|
|
278
|
+
pos += bytesRead;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
pos += lineStart;
|
|
282
|
+
await opts.yieldFn?.();
|
|
283
|
+
}
|
|
284
|
+
return readBytes;
|
|
285
|
+
} finally {
|
|
286
|
+
await handle.close();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/builtins/winston-logging/log-file-index-store.ts
|
|
291
|
+
/**
|
|
292
|
+
* The indexes of every daily file in one logs directory (D898), kept by the
|
|
293
|
+
* reader thread.
|
|
294
|
+
*
|
|
295
|
+
* - `indexOf(file)` brings that file's index up to the bytes written so far
|
|
296
|
+
* (cheap for the live file: only the bytes since the last call) and returns
|
|
297
|
+
* it. A file whose index is still being built answers with the part that
|
|
298
|
+
* exists; the reader scans the rest without skipping.
|
|
299
|
+
* - `backgroundPass()` indexes every file, newest first, yielding between
|
|
300
|
+
* chunks so a read is never queued behind it, and removes sidecars whose
|
|
301
|
+
* log file the rotation deleted.
|
|
302
|
+
*/
|
|
303
|
+
/** Unindexed bytes a read waits for before scanning; more is indexed behind it. */
|
|
304
|
+
var SYNC_CATCH_UP_BYTES = 8 * 1024 * 1024;
|
|
305
|
+
var yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
|
|
306
|
+
var LogFileIndexStore = class {
|
|
307
|
+
logsDir;
|
|
308
|
+
files = /* @__PURE__ */ new Map();
|
|
309
|
+
constructor(logsDir) {
|
|
310
|
+
this.logsDir = logsDir;
|
|
311
|
+
}
|
|
312
|
+
async indexOf(fileName, opts = {}) {
|
|
313
|
+
if (!LOG_FILE_NAME_RE.test(fileName)) return null;
|
|
314
|
+
const logPath = node_path.join(this.logsDir, fileName);
|
|
315
|
+
let stat;
|
|
316
|
+
try {
|
|
317
|
+
stat = await node_fs_promises.stat(logPath);
|
|
318
|
+
} catch {
|
|
319
|
+
this.files.delete(fileName);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
let entry = this.files.get(fileName);
|
|
323
|
+
if (entry === void 0 || entry.index.ino !== stat.ino) {
|
|
324
|
+
const loaded = await loadFileIndex(logPath, stat.ino, stat.size);
|
|
325
|
+
entry = {
|
|
326
|
+
index: loaded.index,
|
|
327
|
+
rewrite: loaded.rewrite,
|
|
328
|
+
busy: Promise.resolve(),
|
|
329
|
+
running: false
|
|
330
|
+
};
|
|
331
|
+
this.files.set(fileName, entry);
|
|
332
|
+
}
|
|
333
|
+
const current = entry;
|
|
334
|
+
if (current.running) return current.index;
|
|
335
|
+
const run = current.busy.then(async () => {
|
|
336
|
+
current.running = true;
|
|
337
|
+
const rewrite = current.rewrite;
|
|
338
|
+
current.rewrite = false;
|
|
339
|
+
try {
|
|
340
|
+
await catchUpFileIndex(logPath, current.index, {
|
|
341
|
+
rewrite,
|
|
342
|
+
yieldFn: yieldToEventLoop
|
|
343
|
+
});
|
|
344
|
+
} catch {
|
|
345
|
+
current.rewrite = true;
|
|
346
|
+
} finally {
|
|
347
|
+
current.running = false;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
current.busy = run;
|
|
351
|
+
if (opts.wait === true || stat.size - indexEnd(current.index) <= SYNC_CATCH_UP_BYTES) await run;
|
|
352
|
+
return current.index;
|
|
353
|
+
}
|
|
354
|
+
/** Index everything, newest first; drop orphaned sidecars. */
|
|
355
|
+
async backgroundPass(shouldStop) {
|
|
356
|
+
let names;
|
|
357
|
+
try {
|
|
358
|
+
names = await node_fs_promises.readdir(this.logsDir);
|
|
359
|
+
} catch {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const logs = new Set(names.filter((n) => LOG_FILE_NAME_RE.test(n)));
|
|
363
|
+
for (const name of names) {
|
|
364
|
+
if (!name.endsWith(".log.idx")) continue;
|
|
365
|
+
const log = name.slice(0, -4);
|
|
366
|
+
if (!logs.has(log)) await node_fs_promises.rm(node_path.join(this.logsDir, name), { force: true });
|
|
367
|
+
}
|
|
368
|
+
for (const name of [...logs].toSorted().toReversed()) {
|
|
369
|
+
if (shouldStop()) return;
|
|
370
|
+
await this.indexOf(name, { wait: true });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
var MAX_BUDGET_MS = 5e3;
|
|
375
|
+
/** Read granularity of a backward scan. */
|
|
376
|
+
var SCAN_CHUNK = 1024 * 1024;
|
|
377
|
+
/** What a reader can rule out from a block alone. */
|
|
378
|
+
function blockMayMatch(block, filter) {
|
|
379
|
+
if (block.l < filter.levelFloor) return false;
|
|
380
|
+
if (filter.until !== null && block.t0 !== null && block.t0 > filter.until) return false;
|
|
381
|
+
if (filter.since !== null && block.t1 !== null && block.t1 < filter.since) return false;
|
|
382
|
+
if (filter.deviceId !== null && block.d !== null && !block.d.includes(filter.deviceId)) return false;
|
|
383
|
+
if (filter.addonId !== null && block.a !== null && !block.a.includes(filter.addonId)) return false;
|
|
384
|
+
if (filter.nodeId !== null && block.o !== null && !block.o.includes(filter.nodeId)) return false;
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
/** The byte strings a matching raw line MUST contain (superset tests). */
|
|
388
|
+
function requiredNeedles(filter) {
|
|
389
|
+
const needles = [];
|
|
390
|
+
if (filter.deviceId !== null) needles.push(Buffer.from(`"deviceId":${String(filter.deviceId)}`));
|
|
391
|
+
if (filter.addonId !== null) needles.push(Buffer.from(`"addonId":"${filter.addonId}`));
|
|
392
|
+
return needles;
|
|
393
|
+
}
|
|
394
|
+
/** A text needle that survives JSON escaping unchanged can pre-filter the raw line. */
|
|
395
|
+
function rawTextNeedle(filter) {
|
|
396
|
+
const text = filter.textLower;
|
|
397
|
+
if (text === null) return null;
|
|
398
|
+
return /["\\\u0000-\u001f]/.test(text) ? null : text;
|
|
399
|
+
}
|
|
400
|
+
/** Sorted occurrence positions of `needle` in `data`. */
|
|
401
|
+
function occurrences(data, needle, from) {
|
|
402
|
+
const out = [];
|
|
403
|
+
let at = data.indexOf(needle, from);
|
|
404
|
+
while (at !== -1) {
|
|
405
|
+
out.push(at);
|
|
406
|
+
at = data.indexOf(needle, at + 1);
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
function isRecord(value) {
|
|
411
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
412
|
+
}
|
|
413
|
+
/** One JSON row → a read entry, or null for a line that is not one of ours. */
|
|
414
|
+
function parseLogRow(text) {
|
|
415
|
+
let raw;
|
|
416
|
+
try {
|
|
417
|
+
raw = JSON.parse(text);
|
|
418
|
+
} catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
if (!isRecord(raw)) return null;
|
|
422
|
+
const { level, message, timestamp, scope, id, tags, meta } = raw;
|
|
423
|
+
if (level !== "debug" && level !== "info" && level !== "warn" && level !== "error") return null;
|
|
424
|
+
if (typeof message !== "string" || typeof timestamp !== "string") return null;
|
|
425
|
+
const ts = Date.parse(timestamp);
|
|
426
|
+
if (!Number.isFinite(ts)) return null;
|
|
427
|
+
const cleanTags = {};
|
|
428
|
+
if (isRecord(tags)) {
|
|
429
|
+
for (const [key, value] of Object.entries(tags)) if (typeof value === "string" || typeof value === "number") cleanTags[key] = value;
|
|
430
|
+
}
|
|
431
|
+
const hasTags = Object.keys(cleanTags).length > 0;
|
|
432
|
+
const scopeText = typeof scope === "string" ? scope : void 0;
|
|
433
|
+
const metaRecord = isRecord(meta) ? meta : void 0;
|
|
434
|
+
return {
|
|
435
|
+
entry: {
|
|
436
|
+
...typeof id === "string" ? { id } : {},
|
|
437
|
+
timestamp: ts,
|
|
438
|
+
level,
|
|
439
|
+
...scopeText !== void 0 ? { scope: scopeText } : {},
|
|
440
|
+
message,
|
|
441
|
+
...hasTags ? { tags: cleanTags } : {},
|
|
442
|
+
...metaRecord !== void 0 ? { meta: metaRecord } : {}
|
|
443
|
+
},
|
|
444
|
+
line: {
|
|
445
|
+
timestamp: ts,
|
|
446
|
+
level,
|
|
447
|
+
message,
|
|
448
|
+
scope: scopeText,
|
|
449
|
+
tags: hasTags ? cleanTags : void 0,
|
|
450
|
+
meta: metaRecord
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/** Daily files, newest first. */
|
|
455
|
+
async function listLogFiles(logsDir) {
|
|
456
|
+
let names;
|
|
457
|
+
try {
|
|
458
|
+
names = await node_fs_promises.readdir(logsDir);
|
|
459
|
+
} catch {
|
|
460
|
+
return [];
|
|
461
|
+
}
|
|
462
|
+
return names.filter((n) => LOG_FILE_NAME_RE.test(n)).toSorted((a, b) => a < b ? 1 : a > b ? -1 : 0);
|
|
463
|
+
}
|
|
464
|
+
/** Regions of [0, end), newest first: the unindexed tail, then the blocks. */
|
|
465
|
+
function regionsBackward(index, end) {
|
|
466
|
+
const out = [];
|
|
467
|
+
const indexed = index === null ? 0 : Math.min(indexEnd(index), end);
|
|
468
|
+
if (end > indexed) out.push({
|
|
469
|
+
start: indexed,
|
|
470
|
+
end,
|
|
471
|
+
block: null
|
|
472
|
+
});
|
|
473
|
+
if (index !== null) for (let i = index.blocks.length - 1; i >= 0; i -= 1) {
|
|
474
|
+
const block = index.blocks[i];
|
|
475
|
+
if (block === void 0 || block.s >= end) continue;
|
|
476
|
+
out.push({
|
|
477
|
+
start: block.s,
|
|
478
|
+
end: Math.min(block.e, end),
|
|
479
|
+
block
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
484
|
+
function overBudget(s) {
|
|
485
|
+
return s.ctx.isCancelled() || (s.ctx.now ?? Date.now)() >= s.deadline;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Scan [start, end) backward, calling `onLine` newest first with each line's
|
|
489
|
+
* text and start offset. `end` must be a line start (or the end of the
|
|
490
|
+
* complete data); a trailing partial line is ignored. Returns the start
|
|
491
|
+
* offset of the oldest line examined, or `start` when the region is done.
|
|
492
|
+
*/
|
|
493
|
+
async function scanBackward(handle, start, end, s, onLine) {
|
|
494
|
+
let hi = end;
|
|
495
|
+
let pending = Buffer.alloc(0);
|
|
496
|
+
let first = true;
|
|
497
|
+
while (hi > start) {
|
|
498
|
+
const lo = Math.max(start, hi - SCAN_CHUNK);
|
|
499
|
+
const chunk = Buffer.allocUnsafe(hi - lo);
|
|
500
|
+
const { bytesRead } = await handle.read(chunk, 0, hi - lo, lo);
|
|
501
|
+
s.scannedBytes += bytesRead;
|
|
502
|
+
let data = pending.length > 0 ? Buffer.concat([chunk.subarray(0, bytesRead), pending]) : chunk.subarray(0, bytesRead);
|
|
503
|
+
if (first) {
|
|
504
|
+
first = false;
|
|
505
|
+
const lastNl = data.lastIndexOf(10);
|
|
506
|
+
data = data.subarray(0, lastNl + 1);
|
|
507
|
+
if (data.length === 0 && lo === start) return {
|
|
508
|
+
reached: start,
|
|
509
|
+
stopped: false
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
let firstStart = 0;
|
|
513
|
+
if (lo !== start) {
|
|
514
|
+
const nl = data.indexOf(10);
|
|
515
|
+
if (nl === -1 || nl === data.length - 1) {
|
|
516
|
+
pending = Buffer.from(data);
|
|
517
|
+
hi = lo;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
firstStart = nl + 1;
|
|
521
|
+
}
|
|
522
|
+
const starts = [];
|
|
523
|
+
for (let p = firstStart; p < data.length;) {
|
|
524
|
+
starts.push(p);
|
|
525
|
+
const nl = data.indexOf(10, p);
|
|
526
|
+
if (nl === -1) break;
|
|
527
|
+
p = nl + 1;
|
|
528
|
+
}
|
|
529
|
+
const occ = s.needles.map((needle) => occurrences(data, needle, firstStart));
|
|
530
|
+
const occPtr = occ.map((o) => o.length - 1);
|
|
531
|
+
for (let k = starts.length - 1; k >= 0; k -= 1) {
|
|
532
|
+
const ls = starts[k] ?? 0;
|
|
533
|
+
const le = (starts[k + 1] ?? data.length) - 1;
|
|
534
|
+
let candidate = true;
|
|
535
|
+
for (let n = 0; n < occ.length; n += 1) {
|
|
536
|
+
const positions = occ[n] ?? [];
|
|
537
|
+
let ptr = occPtr[n] ?? -1;
|
|
538
|
+
while (ptr >= 0 && (positions[ptr] ?? 0) >= le) ptr -= 1;
|
|
539
|
+
occPtr[n] = ptr;
|
|
540
|
+
if (ptr < 0 || (positions[ptr] ?? -1) < ls) candidate = false;
|
|
541
|
+
}
|
|
542
|
+
if (!candidate) continue;
|
|
543
|
+
const text = data.toString("utf8", ls, le);
|
|
544
|
+
if (s.textNeedle !== null && !text.toLowerCase().includes(s.textNeedle)) continue;
|
|
545
|
+
if (onLine(text, lo + ls) === "stop") return {
|
|
546
|
+
reached: lo + ls,
|
|
547
|
+
stopped: true
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
pending = Buffer.from(data.subarray(0, firstStart));
|
|
551
|
+
hi = lo;
|
|
552
|
+
const reached = lo + firstStart;
|
|
553
|
+
if (hi > start && overBudget(s)) return {
|
|
554
|
+
reached,
|
|
555
|
+
stopped: true
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
reached: start,
|
|
560
|
+
stopped: false
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* One backward slice. `entries` newest first; `nextBefore` resumes exactly
|
|
565
|
+
* where this slice stopped.
|
|
566
|
+
*/
|
|
567
|
+
async function readLogFilesBackward(ctx, query) {
|
|
568
|
+
const filter = require_log_read_filter.compileLogFilter(query);
|
|
569
|
+
const limit = query.limit ?? 200;
|
|
570
|
+
const budget = Math.min(query.budgetMs ?? 1e3, MAX_BUDGET_MS);
|
|
571
|
+
const s = {
|
|
572
|
+
filter,
|
|
573
|
+
needles: requiredNeedles(filter),
|
|
574
|
+
textNeedle: rawTextNeedle(filter),
|
|
575
|
+
stats: {
|
|
576
|
+
blocksSkipped: 0,
|
|
577
|
+
regionsRead: 0,
|
|
578
|
+
blocksRead: 0,
|
|
579
|
+
linesParsed: 0
|
|
580
|
+
},
|
|
581
|
+
scannedBytes: 0,
|
|
582
|
+
deadline: (ctx.now ?? Date.now)() + budget,
|
|
583
|
+
ctx
|
|
584
|
+
};
|
|
585
|
+
const from = query.before !== void 0 ? decodeFileCursor(query.before) : null;
|
|
586
|
+
const files = (await listLogFiles(ctx.logsDir)).filter((f) => from === null || f <= from.f);
|
|
587
|
+
const entries = [];
|
|
588
|
+
let newestEnd = null;
|
|
589
|
+
const finish = (nextBefore, done) => ({
|
|
590
|
+
result: {
|
|
591
|
+
entries,
|
|
592
|
+
nextBefore: nextBefore === null ? null : encodeFileCursor(nextBefore),
|
|
593
|
+
nextAfter: newestEnd === null ? null : encodeFileCursor(newestEnd),
|
|
594
|
+
scannedBytes: s.scannedBytes,
|
|
595
|
+
done,
|
|
596
|
+
...ctx.isCancelled() ? { cancelled: true } : {}
|
|
597
|
+
},
|
|
598
|
+
stats: s.stats
|
|
599
|
+
});
|
|
600
|
+
for (const file of files) {
|
|
601
|
+
const filePath = node_path.join(ctx.logsDir, file);
|
|
602
|
+
let size;
|
|
603
|
+
try {
|
|
604
|
+
size = (await node_fs_promises.stat(filePath)).size;
|
|
605
|
+
} catch {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const end = from !== null && file === from.f ? Math.min(from.o, size) : size;
|
|
609
|
+
const index = await ctx.indexOf(file);
|
|
610
|
+
const handle = await node_fs_promises.open(filePath, "r");
|
|
611
|
+
try {
|
|
612
|
+
for (const region of regionsBackward(index, end)) {
|
|
613
|
+
const block = region.block;
|
|
614
|
+
if (block !== null) {
|
|
615
|
+
if (filter.since !== null && block.t1 !== null && block.t1 < filter.since) return finish(null, true);
|
|
616
|
+
if (!blockMayMatch(block, filter)) {
|
|
617
|
+
s.stats.blocksSkipped += 1;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
s.stats.regionsRead += 1;
|
|
622
|
+
if (block !== null) s.stats.blocksRead += 1;
|
|
623
|
+
let reachedSince = false;
|
|
624
|
+
const outcome = await scanBackward(handle, region.start, region.end, s, (text, lineStart) => {
|
|
625
|
+
s.stats.linesParsed += 1;
|
|
626
|
+
const row = parseLogRow(text);
|
|
627
|
+
if (row === null) return "continue";
|
|
628
|
+
if (filter.since !== null && row.line.timestamp < filter.since) {
|
|
629
|
+
reachedSince = true;
|
|
630
|
+
return "stop";
|
|
631
|
+
}
|
|
632
|
+
if (!require_log_read_filter.lineMatches(row.line, filter)) return "continue";
|
|
633
|
+
if (newestEnd === null) newestEnd = {
|
|
634
|
+
f: file,
|
|
635
|
+
o: lineStart + Buffer.byteLength(text, "utf8") + 1
|
|
636
|
+
};
|
|
637
|
+
entries.push(row.entry);
|
|
638
|
+
return entries.length >= limit ? "stop" : "continue";
|
|
639
|
+
});
|
|
640
|
+
if (reachedSince) return finish(null, true);
|
|
641
|
+
if (outcome.stopped) return finish({
|
|
642
|
+
f: file,
|
|
643
|
+
o: outcome.reached
|
|
644
|
+
}, false);
|
|
645
|
+
if (overBudget(s)) return finish({
|
|
646
|
+
f: file,
|
|
647
|
+
o: region.start
|
|
648
|
+
}, false);
|
|
649
|
+
}
|
|
650
|
+
} finally {
|
|
651
|
+
await handle.close();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return finish(null, true);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* One forward slice: lines strictly after `after`, scanned oldest first and
|
|
658
|
+
* returned newest first like every page. `nextAfter` is where the next poll
|
|
659
|
+
* starts; `done` means the scan reached the end of the newest file.
|
|
660
|
+
*/
|
|
661
|
+
async function readLogFilesForward(ctx, query) {
|
|
662
|
+
const filter = require_log_read_filter.compileLogFilter(query);
|
|
663
|
+
const limit = query.limit ?? 200;
|
|
664
|
+
const from = decodeFileCursor(query.after);
|
|
665
|
+
const stats = {
|
|
666
|
+
blocksSkipped: 0,
|
|
667
|
+
regionsRead: 0,
|
|
668
|
+
blocksRead: 0,
|
|
669
|
+
linesParsed: 0
|
|
670
|
+
};
|
|
671
|
+
const files = (await listLogFiles(ctx.logsDir)).filter((f) => from === null || f >= from.f).toReversed();
|
|
672
|
+
const collected = [];
|
|
673
|
+
let scannedBytes = 0;
|
|
674
|
+
let oldestStart = null;
|
|
675
|
+
let reached = from;
|
|
676
|
+
let stoppedEarly = false;
|
|
677
|
+
const now = ctx.now ?? Date.now;
|
|
678
|
+
const deadline = now() + Math.min(query.budgetMs ?? 1e3, MAX_BUDGET_MS);
|
|
679
|
+
outer: for (const file of files) {
|
|
680
|
+
const filePath = node_path.join(ctx.logsDir, file);
|
|
681
|
+
let handle;
|
|
682
|
+
try {
|
|
683
|
+
handle = await node_fs_promises.open(filePath, "r");
|
|
684
|
+
} catch {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
const size = (await handle.stat()).size;
|
|
689
|
+
let pos = from !== null && file === from.f ? Math.min(from.o, size) : 0;
|
|
690
|
+
stats.regionsRead += 1;
|
|
691
|
+
const buf = Buffer.allocUnsafe(SCAN_CHUNK);
|
|
692
|
+
while (pos < size) {
|
|
693
|
+
const { bytesRead } = await handle.read(buf, 0, Math.min(SCAN_CHUNK, size - pos), pos);
|
|
694
|
+
if (bytesRead === 0) break;
|
|
695
|
+
scannedBytes += bytesRead;
|
|
696
|
+
let lineStart = 0;
|
|
697
|
+
for (;;) {
|
|
698
|
+
const nl = buf.indexOf(10, lineStart);
|
|
699
|
+
if (nl === -1 || nl >= bytesRead) break;
|
|
700
|
+
const text = buf.toString("utf8", lineStart, nl);
|
|
701
|
+
const absStart = pos + lineStart;
|
|
702
|
+
lineStart = nl + 1;
|
|
703
|
+
reached = {
|
|
704
|
+
f: file,
|
|
705
|
+
o: pos + lineStart
|
|
706
|
+
};
|
|
707
|
+
stats.linesParsed += 1;
|
|
708
|
+
const row = parseLogRow(text);
|
|
709
|
+
if (row === null || !require_log_read_filter.lineMatches(row.line, filter)) continue;
|
|
710
|
+
if (oldestStart === null) oldestStart = {
|
|
711
|
+
f: file,
|
|
712
|
+
o: absStart
|
|
713
|
+
};
|
|
714
|
+
collected.push(row.entry);
|
|
715
|
+
if (collected.length >= limit) {
|
|
716
|
+
stoppedEarly = true;
|
|
717
|
+
break outer;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (lineStart === 0) break;
|
|
721
|
+
pos += lineStart;
|
|
722
|
+
if (ctx.isCancelled() || now() >= deadline) {
|
|
723
|
+
stoppedEarly = true;
|
|
724
|
+
break outer;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} finally {
|
|
728
|
+
await handle.close();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return {
|
|
732
|
+
result: {
|
|
733
|
+
entries: collected.toReversed(),
|
|
734
|
+
nextBefore: oldestStart === null ? null : encodeFileCursor(oldestStart),
|
|
735
|
+
nextAfter: reached === null ? null : encodeFileCursor(reached),
|
|
736
|
+
scannedBytes,
|
|
737
|
+
done: !stoppedEarly,
|
|
738
|
+
...ctx.isCancelled() ? { cancelled: true } : {}
|
|
739
|
+
},
|
|
740
|
+
stats
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/builtins/winston-logging/log-file-read-protocol.ts
|
|
745
|
+
function isObject(value) {
|
|
746
|
+
return typeof value === "object" && value !== null;
|
|
747
|
+
}
|
|
748
|
+
function isFromReader(value) {
|
|
749
|
+
return isObject(value) && (value["type"] === "result" || value["type"] === "error") && typeof value["id"] === "number";
|
|
750
|
+
}
|
|
751
|
+
function isToReader(value) {
|
|
752
|
+
if (!isObject(value)) return false;
|
|
753
|
+
if (value["type"] === "stop") return true;
|
|
754
|
+
if (value["type"] === "cancel") return typeof value["requestId"] === "string";
|
|
755
|
+
return value["type"] === "read" && typeof value["id"] === "number" && isObject(value["query"]);
|
|
756
|
+
}
|
|
757
|
+
/** Requests cancelled before or while they run, with when, so the set cannot grow. */
|
|
758
|
+
var CancelBook = class {
|
|
759
|
+
at = /* @__PURE__ */ new Map();
|
|
760
|
+
cancel(requestId, now = Date.now()) {
|
|
761
|
+
this.at.set(requestId, now);
|
|
762
|
+
for (const [id, when] of this.at) if (now - when > 6e4) this.at.delete(id);
|
|
763
|
+
}
|
|
764
|
+
isCancelled(requestId) {
|
|
765
|
+
return requestId !== void 0 && this.at.has(requestId);
|
|
766
|
+
}
|
|
767
|
+
settle(requestId) {
|
|
768
|
+
if (requestId !== void 0) this.at.delete(requestId);
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
async function serveRead(logsDir, store, cancels, query) {
|
|
772
|
+
const ctx = {
|
|
773
|
+
logsDir,
|
|
774
|
+
indexOf: (file) => store.indexOf(file),
|
|
775
|
+
isCancelled: () => cancels.isCancelled(query.requestId)
|
|
776
|
+
};
|
|
777
|
+
try {
|
|
778
|
+
const after = query.after;
|
|
779
|
+
return after !== void 0 ? await readLogFilesForward(ctx, {
|
|
780
|
+
...query,
|
|
781
|
+
after
|
|
782
|
+
}) : await readLogFilesBackward(ctx, query);
|
|
783
|
+
} finally {
|
|
784
|
+
cancels.settle(query.requestId);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
//#endregion
|
|
788
|
+
Object.defineProperty(exports, "CancelBook", {
|
|
789
|
+
enumerable: true,
|
|
790
|
+
get: function() {
|
|
791
|
+
return CancelBook;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
Object.defineProperty(exports, "LogFileIndexStore", {
|
|
795
|
+
enumerable: true,
|
|
796
|
+
get: function() {
|
|
797
|
+
return LogFileIndexStore;
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
Object.defineProperty(exports, "isFromReader", {
|
|
801
|
+
enumerable: true,
|
|
802
|
+
get: function() {
|
|
803
|
+
return isFromReader;
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
Object.defineProperty(exports, "isToReader", {
|
|
807
|
+
enumerable: true,
|
|
808
|
+
get: function() {
|
|
809
|
+
return isToReader;
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
Object.defineProperty(exports, "serveRead", {
|
|
813
|
+
enumerable: true,
|
|
814
|
+
get: function() {
|
|
815
|
+
return serveRead;
|
|
816
|
+
}
|
|
817
|
+
});
|